正規表現ひとつでHTMLをパースする

こないだsmartyよりフレキシブルでスマートらしい、PHPのテンプレートエンジン PEAR :: Manual :: HTML_Template_Flexy のコードをちらっと読んだ。
Flexyは初めてページを表示する時に、テンプレートをPHPのコードとして書き出して、2回目以降は書き出したPHPファイルを実行するようになっているのだけれど、そのコンパイラがあまり賢くないのだ。それをちょこっといじって、出力時の修飾子を複数つけられるようにすれば {pagetitle:utf8:u} みたいにして、文字コードを変換したあとURLエンコード、みたいな出力がスマートにかけていいと思う。それでコンパイルするところを追っていた。
ついでにsmartyもどうなってるんだろう、とコードをちらっと見てみてびっくりしたのが、HTMLのパースに preg_match() が多用されていること。こんなてきとうなインプリメントがあるだろうかと思ったのだ。一度まじめにパースしてそれを使うよりも早いのかもしれない。

今日は壊れたHTML(開いてるけど閉じてないタグがあるとか、論理的におかしいやつ)を、ブラウザのように適当に解釈して書き直しているものを探していたら Parsing HTML in Perl というページに行き当たった。そこには I’ve never seen an HTML file parsed with a single line of Perl RegEx before! なんて言葉といっしょにこんなコードが。

foreach $element ($html =~/(.*?)(<(?:(?:!--.*?--)|(?:\/?[a-z0-9_:.-]+(?:\s+[a-z0-9_:.-]+(?:=(?:[^> '"\t\n]+|(?:'.*?')|(?:".*?")))?)*))\s*\/?\s*>)/isg)
 {
  $element = TrimWS($element) if $trim;
  $dict->Add($i++, ParseTag($element, $trim)) if length $element;
 }

で、これを適当に整形して、一行ごとにパースしたものが出力されるようにして、こんなHTML

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="ja" lang="ja">
<head>
  <title>perlの正規表現でHTMLをパースする</title>
  <meta http-equiv="content-type" content="text/html; charset=utf-8" />
  <!--link rel="stylesheet" type="text/css" href="./style.css" media="all" /-->
  </head>
<body>
  ちゃんと動きます...
</body>
</html>

をくわせてみると

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="ja" lang="ja">
<head>
<title>
perlの正規表現でHTMLをパースする
</title>
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
<!--link rel="stylesheet" type="text/css" href="./style.css" media="all" /-->
</head>
<body>
ちゃんと動きます...
</body>
</html>

ちゃんとこう出てきてびっくり。お遊び程度にパースしたいならこれで十分。attributeのパースもしてくれる正規表現もほしい!

というわけでsmartyみたいなてきとうパーサもありかなと思った次第です。


About this entry