ある html ドキュメントについて、html タグを削除したいけれど、特定のタグだけは残しておきたいという場合。
(僕らがいる EC 業界の具体的な例で言えば、楽天用や自社サイト用に作った商品説明を Yahoo! など使用できる html タグに制限があるサイトへ転用したいときにこんな課題にぶつかる。)
例えばこんなキャッチコピーがあったとしよう。
catch_copy.html
<strong>セットで買うとお買い得!</strong><br /> <font style="color:blue;">1個200円が</font><br /> <font style="color:red;"><strong>2個で500円!!!</strong></font><BR>
(ちょっとナンセンスだけど HTML 的には間違ってはいない。)
上記の html ドキュメントから br タグ以外を削除したい。
ここで紹介する例では、道具としては Perl と正規表現を使う。
否定の先読みを使う
行の先頭、行の末尾、単語の区切れ目・・・こういった「実際の文字ではなく文字列の中のある意味を持った位置」のことをアサーションという。
文字列の中の「あ」とか「z」など実体のある文字をアトムという。
また、正規表現(パターンマッチング)の中でしか仕事をしない文字のことをメタ文字というが、メタ文字にも当然、
- アサーションにマッチするのが仕事のメタ文字
- アトムにマッチするのが仕事のメタ文字
の2種類がある。
実際のコードを見よう。
remove_html_tag_except_br.pl
#! /usr/bin/perl use warnings; use strict; # 一気読み $/ = undef; my $html = <>; # 「否定の先読み(?!...)」を使って # br タグだけスルーさせる $html =~ s/<(?!br).*?>//imsg; print $html, "\n";
正規表現を含む処理はこの部分だ。
$html =~ s/<(?!br).*?>//imsg;
正規表現は端から1つずつ見ていくと理解しやすい。
この例で言うと、正規表現エンジンはまずはじめに「<」がマッチするかどうかテストする(ちなみに「<」はアトム)。
「<」がマッチすると「次の2文字が br でない位置」かどうかをテストしなければならない。
それが「(?!br)」の部分だ(つまり「(?!br)」はアサーションにマッチするメタ文字だ)。
「(?!br)」がマッチすると「.*?>」がマッチするかテストする。
「.」はアトムであれば何にでもマッチする。
「*」は直前の「.」が0個以上、何個でもあるという意味を付け加える(装飾する)メタ文字。
「.*」だけだと与えられた文字列の最後までマッチしてしまう。
なので仮に「.*>」で最後を締めくくると、「.*」は一番最後の「>」があるところまでマッチが止まらない。
だから控えめなマッチに切り替えるために「.*」の後に「?」をつけて「.*?>」とする。
こうすると「.*?」は一番最初に出会った「>」にマッチするようになる。
このように「<(?!br).*?>」という正規表現が「br タグにマッチしない HTML タグ」というパターンを表している。
そしてオプションだ。この例では i m s g の4つが使われているが、意図する効果は3つ。
- i オプションで正規表現中の「br」が大文字にも小文字にもマッチするように
- m + s オプションで、「.」が改行文字にもマッチするように
- g オプションでグローバルマッチ(文字列中にマッチするパターンが見つかる限り繰り返しマッチ)するように
このスクリプトに catch_copy.html を食わせると、
$ remove_html_tag_except_br.pl catch_copy.html セットで買うとお買い得!<br /> 1個200円が<br /> 2個で500円!!!<BR>
こうなる。
他のやり方もある(マッチをキャプチャして、リプレイスするときに e オプションを使って Perl コードを埋め込むとか・・・)けど、この場合なら素直に否定の先読みを使うのが一番手っ取り早い。