トップページ

2006年08月17日

HTMLを整形式のXML文書に修正するPHPクラス

ガーロ解任記念(無関係)。夏休みの宿題(嘘)。

HTMLなどにありがちなズサンなマークアップを、整形式のXMLドキュメントに補正するPHP用のクラスライブラリです。
一般的なXHTML変換処理にみられる、タグ書式の修正や不適切に用いられている解析対象記号の置換などに加えて、DTD的構成ルールを用いて、欠落している要素の補完や、要素の親子関係の補正をある程度行なうことができます。構成ルールは任意に指定可能で、これにより例えば、HTMLソースから不要な要素や属性を除去したり、別の要素に置換したりといったことも行えます。
各種のプログラミング言語において様々に提供されているマークアップの補正機能は、PHPでは、PECL拡張モジュール版のTidyなどを利用できますが、残念ながらこれらは、現在の一般的なレンタルサーバのPHP環境では必ずしも用意されているわけではありません。このスクリプトベースのクラスライブラリは、このようなバイナリ拡張モジュールが利用できない環境で、マークアップの補正を行う際の補助となることを意図したものです。

てゆうか、まあこいつのために作ったんだけど。

このクラスライブラリは、Free Software FoundationによるGNU Lesser General Public License(LGPL)のバージョン2.1あるいはそれ以降のバージョンの規約に従いライセンスされます。

応用例

» HTMLをXML化してDOMやXPathで操作するWebスクレイピング用PHPクラス

仕様とか制限事項

リリース

» 1.2.1 (stable) released 2007/05/17

※バージョン1.1からは、それ以前のバージョンと異なり、HTML_Sax3クラスのオブジェクトを別途に作成する必要がなくなりました。

ファイル名概要
HTMLParser.class.phpクラスライブラリ本体。
xhtml1-transitional_dtd.inc.phpXHTML 1.0 TransitionalのDTDを基に作成した、サンプルの構成ルールを表す連想配列を返す外部PHPファイル。
sample.phpHTMLを読み込み、補正結果を表示するサンプルアプリケーション。
ファイル中の、ターゲットのURIを指定している箇所を編集して、お好みのウェブページを表示してみてください。また、冒頭にあるふたつの変数を、output_xmlをTRUEに、output_xmlnsをFALSEにすると、Mozilla FirefoxなどではXMLドキュメントツリーを確認できると思います。

概要

このクラスライブラリは、SAXベースのXMLパーサである、PEARのXML_HTMLSax3用のイベントハンドラを定義したものです(従ってその利用にはXML_HTMLSax3が必要です)。XML_HTMLSax3は、PHPのネイティブXMLパーサと異なり、不適切なマークアップに対しても処理を継続します。XML_HTMLSax3は、一般的なSAXパーサと同様に、パース対象のドキュメント中に開始タグや終了タグ、キャラクターデータなどの要素を検出する都度、各要素に対応するハンドラを呼び出します。このクラスライブラリの各ハンドラメソッドは、XML_HTMLSax3から引き渡された各要素を、指定の構成ルールに従って文字列に構築していきます。

サンプルプログラム

以下のダメダメHTMLをパースする簡単なサンプルプログラムを見ていきます。
以下のHTMLはShift_JISエンコーディングで、sample.htmlという名称のファイルで存在しているものとします。

<META NAME=DESCRIPTION CONTENT=Foo&Bar>
<H1>
  <DIV ALIGN=CENTER>Foo&Bar</DIV>
</H1>
<HR NOSHADE>
</BODY>
<P>Foo<BR>Bar
<TABLE FOO=BAR>
<TD>Foo&Bar</TR></TR></TR></TR>
</TABLE>
<LI>Foo
<LI>Bar
<FOO>
<HTML>

まず、このHTMLを読み込み、UTF-8エンコーディングに変換しておきます(XML_HTMLSax3が扱える文字エンコーディングはUTF-8のみです)。

$source = file_get_contents('./sample.html');
mb_convert_variables('UTF-8', 'Shift_JIS', $source);

このクラスのオブジェクトを作成し、アーカイブに付属している、サンプルの構成ルールを表す連想配列を返す外部PHPファイル(xhtml1-transitional_dtd.inc.php)を指定します。このサンプルの構成ルールはXHTML 1.0 TransitionalのDTDを基に作成したものです。setRuleFileメソッドは外部ファイルを指定しますが、これに替えて、setRuleメソッドで配列を指定することもできます。

require_once('./HTMLParser.class.php');
$parser = new HTMLParser;
$parser->setRuleFile('./xhtml1-transitional_dtd.inc.php');

このクラスのオプションを指定します。

$parser->setGenericParent('body');
$parser->setRoot('html',
  array('xmlns' => 'http://www.w3.org/1999/xhtml', 'xml:lang' => 'ja')
);

パースを実行します。

$parser->parse($source);

パース結果の出力を得ます。オプションの文字エンコーディングが指定されている場合、mbstringが利用可能であれば、文字列は指定した文字エンコーディングに変換されます。オプションが未指定の場合、及びmbstringが利用不可の場合、文字エンコーディングはUTF-8となります。

$result = $parser->dump('Shift_JIS');

この出力は以下のようになります(改行とインデントは見易いように追加したもので、実際はこの通りではありません)。

<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="ja">
  <head>
    <meta name="DESCRIPTION" content="Foo&amp;Bar" />
  </head>
  <body>
    <div align="CENTER">
      <h1>Foo&amp;Bar</h1>
    </div>
    <hr noshade="noshade" />
    <p>Foo<br />Bar</p>
    <table>
      <tr><td>Foo&amp;Bar</td></tr>
    </table>
    <ul>
      <li>Foo</li>
      <li>Bar</li>
    </ul>
  </body>
</html>

この例では以下のような処理が施されています。

しかし一方、これを例えばXHTML 1.0 TransitionalのDTDで検証した場合、以下のような不備があり、これらは、このクラスライブラリの処理の限界の例です。

なお、ドキュメントのパース結果は文字列として返されますが、幾つかのタグについてはより利便性の高い形式でその属性値を取得したい場合があるかもしれません。例えば、HTMLを解析する際にはしばしばMETAやLINKの属性値を得る必要がありますが、これらをパース結果の文字列から再度取得し直すのは非効率的です。
このクラスライブラリでは、あらかじめ指定されたタグについて、その属性値を連想配列に保存し、パース結果とは別途にこれを取得する手段を用意しています。

まず、パースを開始する前に、setTagsToSaveメソッドで属性値を保存するタグ名を指定しておきます。タグ名は、以下のような文字列、または配列のいずれでも指定でき、両者の混在も可能です。

$parser->setTagsToSave('meta', 'link');

パース後にgetSavedTagsメソッドで保存された連想配列を得ることができます。オプションの文字エンコーディングが指定されている場合、mbstringが利用可能であれば、文字列は指定した文字エンコーディングに変換されます。オプションが未指定の場合、及びmbstringが利用不可の場合、文字エンコーディングはUTF-8となります。

$saved_tags = $parser->getSavedTags('Shift_JIS');

以下は例として、取得した配列からMobile Link DiscoveryのURIを得るものです。

if (isset($saved_tags['link'])) {
  foreach ($saved_tags['link'] as $value) {
    if (isset($value['href'])
      and isset($value['rel'])
      and isset($value['media'])
      and strtolower($value['rel']) == 'alternate'
      and strtolower($value['media']) == 'handheld') {
      $mobile_link = $value['href'];
      break;
    }
  }
}
if (isset($mobile_link)) {
  var_dump($mobile_link);
}

リファレンス

void HTMLParser->setRuleFile ( string filename )
構成ルールを表す連想配列を返す外部PHPファイルを指定します。
連想配列及び外部ファイルの詳細はアーカイブに付属のサンプルを参照してください。
void HTMLParser->setRule ( array )
構成ルールを表す連想配列を指定します。
連想配列の詳細はアーカイブに付属のサンプルを参照してください。
string HTMLParser->dump ( [string to_encoding] )
パース結果の文字列を返します。オプションの文字エンコーディングが指定されている場合、mbstringが利用可能であれば、文字列は指定した文字エンコーディングに変換されます。オプションが未指定の場合、及びmbstringが利用不可の場合、文字エンコーディングはUTF-8となります。
void HTMLParser->setRoot ( string tag_name [, array tag_attributes] )
ドキュメントのルート要素を指定します。パーサから開始タグのハンドラに最初に渡されるタグ名がここで指定した名前と合致しない場合は、指定の値に基づく要素が補完され、ハンドラから渡された要素は、その子要素として評価されます(なお、属性値は評価対象ではありません。名前さえ合致していれば、その属性値に関わらずハンドラから渡された要素がそのまま利用されます)。
void HTMLParser->setGenericParent ( string tag_name )
要素の親子関係の補正を行う際には、構成ルールに定義される、各タグの「既定の親(default_parent)」が参照される場合があります。HTMLでは、多くのタグについてこれは「body」ですが、これらのタグについて構成ルールに逐次「既定の親」を記述するのは効率が悪いので、「既定の親」が未定義のタグについては、ここで指定したタグが「既定の親」となります。
void HTMLParser->setTagsToSave ( mixed tag_name )
このメソッドによって指定したタグの属性値は連想配列に保存され、パース後にgetSavedTagsメソッドによってアクセスすることができるます。タグの指定は、タグがひとつなら文字列で、複数ある場合はカンマ区切りの文字列または配列で行い、文字列と配列は混在することができます。
array HTMLParser->getSavedTags ( string to_encoding )
setTagsToSaveメソッドで指定したタグの属性値を保存した連想配列を返します。オプションの文字エンコーディングが指定されている場合、mbstringが利用可能であれば、文字列は指定した文字エンコーディングに変換されます。オプションが未指定の場合、及びmbstringが利用不可の場合、文字エンコーディングはUTF-8となります。

ACKNOWLEDGMENT

The idea of this software is in fact inspired by PEAR XML_DTD maintained by Thomas V.V.Cox and Stephan Schmidt. And, of course, this software is greately depends on PEAR XML_HTMLSax3 maintained by Harry Fuecks. I would like to thank them all.

Category: ウェブ制作
Posted 2006年08月17日 21:54

トラックバック

このエントリーのトラックバックURL:
http://www.rcdtokyo.com/mt/mt-rcdtokyo5428-tb.cgi/770

このリストは、次のエントリーを参照しています: HTMLを整形式のXML文書に修正するPHPクラス:

» [PHP][雑記]PHPで街を育てる from Do You PHP はてな
via. Rauru Blog » Blog Archive » Ruby とか Perl とかで街を育てる 空前のMyMiniCi... [続きを読む]

Pinged at 2007/12/20 13:14

» [11.開発日記][php][HTMLScraping][RSS]String could not be parsed as XMLの対策 from [Mi]みたいなもの
String could not be parsed as XMLの原因について、肝心の対策を書いてなかったので書いておきます。 (HTMLScrapi... [続きを読む]

Pinged at 2009/07/16 12:31

» Google検索順位取得:PHPで「Google検索の順位取得」を試してみた。こりゃまた便利なクラスライブラリを利用 ・・・ 【PHP】 from 創るmetaboy
さて、先日  Googleのページランクを調べるツールを創っ... [続きを読む]

Pinged at 2009/07/30 18:02

コメント

便利!ですので、使わせてもらっていますが、当初エラーが出たので、なぜだ?と思ったら、preg_match_all()に使っている定数PREG_OFFSET_CAPTUREがPHP4.3.3以前では使えないようです。(PHP公式マニュアルでは4.3.0から使えることになってますが誤りの用です)。PHPをバージョンアップしたら使えました。一応ご報告でした。

Posted by 通りすがりさん at 2007/08/07 12:19

ああ、すいません4.3.0とか書いてますねえ…。
PCサイト→携帯変換スクリプトの制作初期に、PREG_OFFSET_CAPTUREの罠にはまったことがあったっけと、今思いだしますた(笑)

Posted by ucbさん at 2007/08/07 23:19

コメントをどうぞ



保存しますか?


Aoaka Style Valid Aoaka