正規表現の検索・置換をHTMLで実践
見出しのテキストを取得する
見出し「h1」のテキストを正規表現で取得してみましょう。
<h1>Linuxサーバー管理入門</h1>
正規表現で見出し「h1」のテキストである「UNIX/Linuxサーバー管理入門」を取得するプログラム「head_text.pl」です。ソースコードはUTF-8で保存してください。
use strict; use warnings; use utf8; use Encode 'encode'; my $html = '<h1>Linuxサーバー管理入門</h1>'; my $head_text; if ($html =~ /<h1>(.+)<\/h1>/) { $head_text = $1; } print encode('UTF-8', "$head_text") . "\n";
Windowsの場合はencode関数の「UTF-8」の部分を「cp932」にしてみてください(head_text_win.pl)。この後のサンプルも同じです。
use strict; use warnings; use utf8; use Encode 'encode'; my $html = '<h1>Linuxサーバー管理入門</h1>'; my $head_text; if ($html =~ /<h1>(.+)<\/h1>/) { $head_text = $1; } print encode('cp932', "$head_text") . "\n";
実行してみましょう。
perl head_text.pl
または
perl head_text_win.pl
出力結果です。見出しを取り出せています。
Linuxサーバー管理入門
見出しのテキストを取得する - 最短マッチ
先ほどの見出しを取得するスクリプト、HTMLが次のようになっていたらどうなるんでしょうか?
<h1>Linuxサーバー管理入門</h1>まちがえ閉じタグ</h1>
先ほどのスクリプトを実行すると、以下の出力になります。
Linuxサーバー管理入門</h1>まちがえ閉じタグ
あれ、後ろの方の</h1>の直前までが取り出されてしまいました。
「*」や「+」などのPerlの正規表現の量指定子は、デフォルトで最も長い位置までマッチします。
これを、最も短い位置でマッチさせるようにするには、正規表現を以下のように書き変えます。量指定子の後ろに「?」をつけるのがポイントです。
$html =~ /<h1>(.+?)<\/h1>
では、プログラムを書き直してみましょう。最も短い位置でマッチする「head_text_short_match.pl」です。
use strict; use warnings; use utf8; use Encode 'encode'; my $html = '<h1>Linuxサーバー管理入門</h1>まちがえ閉じタグ</h1>'; my $head_text; if ($html =~ /<h1>(.+?)<\/h1>/) { $head_text = $1; } print encode('UTF-8', "$head_text") . "\n";
実行してみましょう。
perl head_text_short_match.pl
今度は正しく欲しい部分を取得することができました。
Linuxサーバー管理入門
HTMLからタグを削除する
正規表現で置換の構文を使って、HTMLからタグを削除してテキストの部分だけを取り出してみましょう。
今日は<b>プログラミング</b>を学んで充実した一日。今度は<span style="font-size:18px">Webシステム開発構築</span>を行いたいです。
以下のようなタグを取り除くことができる正規表現を考えます。
<b> </b> <span style="font-size:18px"> </span>
正規表現を使って、タグを取り除くプログラム「remove_tag.pl」です。
use strict; use warnings; use utf8; use Encode 'encode'; my $html = '今日は<b>プログラミング</b>を学んで充実した一日。今度は<span style="font-size:18px">Webシステム開発構築</span>を行いたいです。'; # 元のHTMLを残しておくために文字列を置換前にコピーする my $text = $html; # <と>で囲まれている部分をすべて削除する $text =~ s/<.+?>//g; print encode('UTF-8', "$text") . "\n";
正規表現の解説です。「<」がきて、改行以外のなんでもよい文字が1文字以上で最短マッチ「.+?」、「>」がくる。gオプションは、すべて置換を意味しています。
実行してみましょう。
perl remove_tag.pl
出力結果です。タグが取り除かれています。
今日はプログラミングを学んで充実した一日。今度はWebシステム開発構築を行いたいです。
前後の空白文字を取り除く
前後の空白文字を取り除いてみましょう。目には見えませんが、全角のスペースと半角のスペースが、前後に複数含まれています。
Linuxサーバー管理入門
前後の空白文字を取り除くには、「先頭から複数の空白を取り除く」「末尾から複数の空白を取り除く」という二つの正規表現を書きます。
# 前後の空白文字を取り除く $text =~ s/^\s+//; $text =~ s/\s+$//;
前後の空白文字を取り除く「trim.pl」です。
use strict; use warnings; use utf8; use Encode 'encode'; my $text = ' Linuxサーバー管理入門 '; # 先頭から複数の空白を取り除く $text =~ s/^\s+//; # 末尾から複数の空白を取り除く $text =~ s/\s+$//; print encode('UTF-8', "$text") . "\n";
実行してみましょう。
perl trim.pl
出力結果です。前後の空白が取り除けています。
Linuxサーバー管理入門
「\s」という正規表現は、Unicodeで空白文字として定義されている文字を意味します。ですので、ASCIIコードの空白文字である、半角スペース、タブ、改行に加え、Unicodeで空白文字として定義されている、日本語の全角スペースも対象になります。
もしASCIIコードで空白とみなされる空白だけを意味したいのであれば、次のように書きます。特殊な文字クラスである「\p{PosixSpace}」を使って表現できます。
ASCIIコードの空白だけを取り除く「trim_ascii.pl」です。
# 先頭から複数の空白を取り除く $text =~ s/^\p{PosixSpace}+//; # 末尾から複数の空白を取り除く $text =~ s/\p{PosixSpace}$//;
実行してみましょう。
perl trim_ascii.pl
出力結果です。ASCIIの空白だけが取り除けています。全角の空白は残っています。
Linuxサーバー管理入門
Perl 5.14以降であれば「\s」にASCIIコードの空白のみを意味させる「a」オプションを使って以下のように簡単に書けます。サンプルは「trim_ascii_simple.pl」です。
# 先頭から複数の空白を取り除く $text =~ s/^\s+//a; # 末尾から複数の空白を取り除く $text =~ s/\s+$//a;
実行してみましょう。
perl trim_ascii_simple.pl
出力結果です。ASCIIの空白だけが取り除けています。全角の空白は残っています。
Linuxサーバー管理入門
pタグで作られた複数行の段落から本文だけを取り出す
複数行のpタグで作られた段落から本文だけを取り出すということをやってみましょう。先頭、末尾の空白は削除して、間の複数の空白はひとつの空白に置換してみましょう。
<p> 今日は、毎週土曜日更新のPerlプログラミングちゃんねるを見ています。 ひげで、坊主のオジサンが雑にしゃべっているのが気になり、講義が頭の中に入ってきません。 </p>
正規表現を書いてみましょう。「.」は「改行以外のすべての文字」にマッチするのですが、今回の場合は、改行を含めたすべての文字をマッチさせる必要があります。このような場合は、「.」をすべての文字にマッチさせるようにする「s」オプション(シングルラインオプション)を使用します。最短マッチでマッチさせます。
my $text; if ($html =~ /<p>(.*?)<\/p>/s;) { $text = $1; # 先頭の空白文字を削除 $text =~ s/^\s+//; # 末尾の空白文字を削除 $text =~ s/\s+$//; # 複数の空白文字を一つの空白へ $text =~ s/\s+/ /; }
サンプルコード全体「get_p_text.pl」です。
use strict; use warnings; use utf8; use Encode 'encode'; my $html = <<'EOS'; <p> 今日は、毎週土曜日更新のPerlプログラミングちゃんねるを見ています。 ひげで、坊主のオジサンが雑にしゃべっているのが気になり、講義が頭の中に入ってきません。 </p> EOS my $text; if ($html =~ /<p>(.*?)<\/p>/s) { $text = $1; # 先頭の空白文字を削除 $text =~ s/^\s+//; # 末尾の空白文字を削除 $text =~ s/\s+$//; # 複数の空白文字を一つの空白へ $text =~ s/\s+/ /; } print encode('UTF-8', "$text") . "\n";
実行してみましょう。
perl get_p_text.pl;
出力結果です。本文だけが取り出せています。
今日は、毎週土曜日更新のPerlプログラミングちゃんねるを見ています。 ひげで、坊主のオジサンが雑にしゃべっているのが気になり、講義が頭の中に入ってきません。
複数行の文字列を表現する場合に、ヒアドキュメントという機能をここでは使っています。
# ヒアドキュメントで複数行を表現 my $html = <<'EOS'; <p> 今日は、毎週土曜日更新のPerlプログラミングちゃんねるを見ています。 ひげで、坊主のオジサンが雑にしゃべっているのが気になり、講義が頭の中に入ってきません。 </p> EOS
EOSの囲み文字を「"」に変えると、変数展開を行えるようになるので覚えておきましょう。
# ヒアドキュメントで複数行を表現(変数展開あり) my $language = 'Perl'; my $html = <<"EOS"; <p> 今日は、毎週土曜日更新の${language}プログラミングちゃんねるを見ています。 ひげで、坊主のオジサンが雑にしゃべっているのが気になり、講義が頭の中に入ってきません。 </p> EOS
HTMLファイルを処理する
今度は、HTMLファイルの全体を処理する方法について学んでみましょう。
HTMLファイル全体を読み込む
HTMLファイル全体を一度に読み込んで、処理してみましょう。
ファイルの内容を一度に読み込むには、まず、行入力演算子のセパレーターを表現する特殊変数「$/」を未定義値「undef」にします。localは、特殊変数などのグローバル変数を一時的に変更したい場合に、使用します。localで、変更した内容は「{}」で囲まれたスコープの終わりで、復元されます。
# 行入力演算子のセパレーター「$/」を未定義にする local $/ = undef;
次に、行入力演算子で、読み込みます。
# 行入力演算子で読み込む $html = <>;
ファイルから読み込んだ内容は、decode関数でデコードして、Perlの内部文字列にします。
# デコードしてPerlの内部文字列へ変換 $html = decode('UTF-8', $html);
上記の内容を一度に書くと、次のようになります。
# ファイル全部を読み込む my $html; { local $/ = undef; $html = <>; $html = decode('UTF-8', $html); }
では、サンプルコードを書いてみましょう。ます、入力ファイル「proc_html_in.html」を、以下の内容で、UTF-8で保存してください。
<p> 今日は、毎週土曜日更新のPerlプログラミングちゃんねるを見ています。 ひげで、坊主のオジサンが雑にしゃべっているのが気になり、講義が頭の中に入ってきません。 </p>
HTMLファイルを読み込んで処理して、出力するサンプルです。
use strict; use warnings; use utf8; use Encode 'encode', 'decode'; # ファイル全部を読み込む my $html; { local $/ = undef; $html = <>; $html = decode('UTF-8', $html); } my $text; if ($html =~ /<p>(.*?)<\/p>/s) { $text = $1; # 先頭の空白文字を削除 $text =~ s/^\s+//; # 末尾の空白文字を削除 $text =~ s/\s+$//; # 複数の空白文字を一つの空白へ $text =~ s/\s+/ /; } print encode('UTF-8', $text);
次のように実行してください。
perl proc_html.pl proc_html_in.html > proc_html_out.html
「proc_html_out.html」ファイルには、以下の内容が出力されます。
今日は、毎週土曜日更新のPerlプログラミングちゃんねるを見ています。 ひげで、坊主のオジサンが雑にしゃべっているのが気になり、講義が頭の中に入ってきません。
すべてのバナナをリンゴに置換する
テキストファイル「replace_all_in.html」(UTF-8で保存)に含まれるすべてのバナナをリンゴに置換してみましょう。
モジガエルは、バナナを食べています。 バナナは、まだ青く熟していません。 「早く、バナナが食べごろにならないかなぁ」
すべて置換するには、gオプションを使用します。
# すべてのバナナをリンゴに置換 $out_html =~ s/バナナ/リンゴ/g;
テキストファイルに含まれるすべてのバナナをリンゴに置換するプログラム「replace_all.pl」です。
use strict; use warnings; use utf8; use Encode 'encode', 'decode'; # ファイル全部を読み込む my $in_html; { local $/ = undef; $in_html = <>; $in_html = decode('UTF-8', $in_html); } # ファイルの内容を出力用のHTMLにコピー my $out_html = $in_html; # すべてのバナナをリンゴに置換 $out_html =~ s/バナナ/リンゴ/g; print encode('UTF-8', $out_html);
次のようにプログラムを実行します。
perl replace_all.pl replace_all_in.html > replace_all_out.html
出力ファイル「replace_all_out.html」の中身は以下のようになり、バナナがリンゴに置換されています。
モジガエルは、リンゴを食べています。 リンゴは、まだ青く熟していません。 「早く、リンゴが食べごろにならないかなぁ」
青リンゴ、赤リンゴ、黄リンゴの数を数える
次のテキスト「count_color_in.html」に含まれている青リンゴ、赤リンゴ、黄リンゴの数を数えてみましょう。
青リンゴ、赤リンゴ、黄リンゴ、どれが好き。 うーん、赤リンゴが、食べたいなぁ。赤リンゴは、すっかり食べごろ。 青リンゴも、酸味がすっきり、おいしそう。
すべて検索をするには、while構文で、正規表現のgオプションを使います。
青リンゴ、赤リンゴ、黄リンゴの数を数えるプログラム「count_color.pl」です。
use strict; use warnings; use utf8; use Encode 'encode', 'decode'; # ファイル全部を読み込む my $html; { local $/ = undef; $html = <>; $html = decode('UTF-8', $html); } # リンゴの色の個数を数える my %apple_counts; while ($html =~ /(青|赤|黄)リンゴ/g) { my $color = $1; # 個数を加算 $apple_counts{$color}++; } # リンゴの個数を記述したテキスト my $apple_counts_str = ''; for my $color (sort keys %apple_counts) { my $apple_count = $apple_counts{$color}; $apple_counts_str .= "${color}リンゴ: ${apple_count}個\n"; } print encode('UTF-8', $apple_counts_str);
次のように実行してみましょう。
perl count_color.pl count_color_in.html > count_color_out.html
出力ファイルには、以下のように出力されます。リンゴの数を数えられていますね。
赤リンゴ: 3個 青リンゴ: 2個 黄リンゴ: 1個
全角数字を半角数字に変換する
全角数字を半角数字に変換するサンプルです。正規表現ではないのですがtr演算子と呼ばれる、対応する文字を置換する演算子を使うと簡単にできます。
次のテキスト「repnum_zen_to_han_in.html」に含まれている全角数字を半角数字に変換してみましょう。
モジガエル君。君の好きな果物を5つ書いてください。 1 バナナ 2 リンゴ 3 いちご 4 ぶどう 5 みかん
全角数字を半角数字に変換する「repnum_zen_to_han.pl」です。
use strict; use warnings; use utf8; use Encode 'encode', 'decode'; # ファイル全部を読み込む my $in_html; { local $/ = undef; $in_html = <>; $in_html = decode('UTF-8', $in_html); } # ファイルの内容を出力用のHTMLにコピー my $out_html = $in_html; # 全角数字を半角数字に置換 $out_html =~ tr/0-9/0-9/; print encode('UTF-8', $out_html);
次のように実行してみましょう。
perl repnum_zen_to_han.pl repnum_zen_to_han_in.html > repnum_zen_to_han_out.html
出力ファイルには、以下のように出力されます。全角数字が、半角文字に変換されていますね。
モジガエル君。君の好きな果物を5つ書いてください。 1 バナナ 2 リンゴ 3 いちご 4 ぶどう 5 みかん
小文字の単語を大文字の単語に変換する
正規表現の置換の構文を使って、英語の小文字の単語を大文字の単語に変換してみましょう。
Perlの置換の構文では、eオプションを使って、置換に式を利用できます。キャプチャした英単語をuc関数で大文字に変換します。eの意味は、executeという意味だと思われます。すべて置換するのでgオプションも指定します。
# すべての小文字を大文字に置換 $out_html =~ s/([a-z]+)/uc $1/eg;
次のテキスト「replace_lc_to_uc_in.html」に含まれている全角数字を半角数字に変換してみましょう。
私はappleを持っています。 私はpenを持っています。 うーん、apple pen。
小文字に大文字に変換する「replace_lc_to_uc.pl」です。
use strict; use warnings; use utf8; use Encode 'encode', 'decode'; # ファイル全部を読み込む my $in_html; { local $/ = undef; $in_html = <>; $in_html = decode('UTF-8', $in_html); } # ファイルの内容を出力用のHTMLにコピー my $out_html = $in_html; # すべての小文字を大文字に置換 $out_html =~ s/([a-z]+)/uc $1/eg; print encode('UTF-8', $out_html);
次のように実行してみましょう。
perl replace_lc_to_uc.pl replace_lc_to_uc_in.html > replace_lc_to_uc_out.html
出力ファイルには、以下のように出力されます。小文字の英単語が、大文字の英単語に変換されています。
私はAPPLEを持っています。 私はPENを持っています。 うーん、APPLE PEN。
すべての外部URLを取り出す
正規表現を使って、すべての外部URLを取り出してみましょう。
次のテキスト「get_url_in.html」に含まれているhttpまたはhttpsで始まる外部URLを取り出します。
<a href="https://perlclub.net">Perlクラブ</a> <a href="https://www.perlzemi.com">Perlゼミ</a> <a href="ftp://www.perlzemi.com">FTPのURL</a>
外部URLの条件は以下のように決めます。
・「href="http://foo.bar"」のように「href="URL"」となっている。
・href = "http://foo.bar" のように、「=」の前後はスペースを許可
・URLは、http://あるいはhttps://で始まる。
正規表現の部分です。
# httpまたはhttpsで始まる外部URLをすべて取得 my @urls; while ($in_html =~ m|href\s*=\s*"(https?://[^"]+)"|g) { my $url = $1; push @urls, $url; }
すべて取り出すのでwhile文とgオプションの組み合わせを使います。
「m|正規表現|」は「/正規表現/」と同じ意味ですが、正規表現の中でそのまま「/」が使えます。
「\s*」は空白が0文字以上。
「https?」は「s」があってもなくてもよいという意味なので「http」あるいは「https」と読みます。
「[^"]」は文字クラスの否定です。「"」以外の任意の文字と読みます。「[^"]+」は、「"」以外の任意の文字の1文字以上と読みます。URLの終わりの次の文字が「"」なので、その文字が出現しない間、読み込みます。
httpまたはhttpsで始まる外部URLを取り出す「get_url.pl」です。
use strict; use warnings; use utf8; use Encode 'encode', 'decode'; # ファイル全部を読み込む my $in_html; { local $/ = undef; $in_html = <>; $in_html = decode('UTF-8', $in_html); } # httpまたはhttpsで始まる外部URLをすべて取得 my @urls; while ($in_html =~ m|href\s*=\s*"(https?://[^"]+)"|g) { my $url = $1; push @urls, $url; } # URLを出力 for my $url (@urls) { print encode('UTF-8', "$url\n"); }
次のように実行してみましょう。
perl get_url.pl get_url_in.html > get_url_out.html
出力ファイルには、以下のように出力されます。httpまたはhttpsで始まる外部URLが取り出せています。
https://perlclub.net https://www.perlzemi.com
ファイル全部を読み込むより簡単な方法
この章では、以下のようにファイルの内容を一度に読み込んでいました。
# ファイル全部を読み込む my $html; { local $/ = undef; $html = <>; $html = decode('UTF-8', $html); }
これは、行入力演算子「<>」を学んだ後に、行入力セパレーター「$/」を未定義「undef」にすれば、ファイルの全部を読み込めるという解説をしたためです。もう少し短くすることは可能です。
Perlには、doブロック「do {}」と呼ばれる、最後に評価された値を返す機能があります。また「local $/;」は「local $/ = undef;」と同じ意味です。
Perlに慣れてくると、ファイル全体の読み込みは、一般的に次のように書かれることが多いです。初めて見ると「なんじゃこりゃー」と思いますね...。
# ファイル全部を読み込む my $html = do { local $/; <> }; # ファイルの内容をデコード $html = decode('UTF-8', $html);
上記の簡単な書き方で、ファイルの内容を読み込んで、そのまま出力するサンプルを作成してみましょう。
入力ファイル「get_whole_file_in.html」は以下にします。
モジガエルは、池で水浴びをしています。 涼しそうで、気持ちよさそうですね。
HTMLファイルを読み込んで処理して、出力するサンプル「get_whole_file_simple.pl」です。
use strict; use warnings; use utf8; use Encode 'encode', 'decode'; # ファイル全部を読み込む my $html = do { local $/; <> }; # ファイルの内容をデコード $html = decode('UTF-8', $html); # ファイルの内容を出力 print encode('UTF-8', $html);
次のように実行してみてください。
perl get_whole_file.pl get_whole_file_in.html > get_whole_file_out.html
出力結果です。
モジガエルは、池で水浴びをしています。 涼しそうで、気持ちよさそうですね。