PHPでHTMLをパースするには、PECL::Tidyを使う方法やDOM拡張モジュールを使う方法、「HTMLを整形式のXML文書に修正するPHPクラス : Under Construction, Baby」で配布されているようなライブラリなどを使う方法などがある。 この中でDOM拡張モジュールは、PHP5からPHPコアに含まれているため他の方法に比べて利用しやすい。


DOM拡張モジュールでHTMLをパースするにはDOMDocument->loadHTML()を使う。 HTMLを読み込むためのメソッドなので、閉じられていないタグがあっても読み込むことができる。

<?php
$html = <<<EOD
<html>
	<head>
		<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
		<title>誰でもわかるプログラミング入門。</title>
	</head>
	<body>
		<h2>目次</h2>
		<ul>
			<li><b>Step1.</b>ぐぐれ</li>
			<li><b>Step2.</b>頑張れ
		</ul>
	</body>
</html>
EOD;

//HTMLをパースする
$doc = new DOMDocument();
$doc->loadHTML($html);

//bodyにdivを追加する
$doby = $doc->getElementsByTagName('body')->item(0);
$div = $doc->createElement('div');
$div->setAttribute('style', 'border: solid 1px #ff0000; padding: 10px;');
$div->nodeValue = '※このページはフィクションです。';
$doby->appendChild($div);

//HTMLを出力
echo $doc->saveHTML();
?>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>誰でもわかるプログラミング入門。</title>
</head>
<body>
		<h2>目次</h2>
		<ul>
<li>
<b>Step1.</b>ぐぐれ</li>
			<li>
<b>Step2.</b>頑張れ
		</li>
</ul>
<div style="border: solid 1px #ff0000; padding: 10px;">※このページはフィクションです。</div>
</body>
</html>

このモジュールはDOM Level 3の仕様に従って作られているため、getElementByIdメソッドgetElementsByTagNameメソッドなどを使って、JavaScriptと同じようにDOMを操作することができる。

ただし、このときmetaタグで文字エンコードが指定されていないと日本語が文字化けをしてしまう。

<?php
$html = <<<EOD
<html>
	<head>
		<title>誰でもわかるプログラミング入門。</title>
	</head>
	<body>
		<h2>目次</h2>
		<ul>
			<li><b>Step1.</b>ぐぐれ</li>
			<li><b>Step2.</b>頑張れ</li>
		</ul>
	</body>
</html>
EOD;

//HTMLをパースする
$doc = new DOMDocument();
$doc->loadHTML($html);

//HTMLを出力
echo $doc->saveHTML();	//←文字化けしている
?>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd">
<html>
<head><title>&egrave;&ordf;&deg;&atilde;&#129;&sect;&atilde;&#130;&#130;&atilde;&#130;&#143;&atilde;&#129;&#139;&atilde;&#130;&#139;&atilde;&#131;&#151;&atilde;&#131;&shy;&atilde;&#130;&deg;&atilde;&#131;&copy;&atilde;&#131;&#159;&atilde;&#131;&sup3;&atilde;&#130;&deg;&aring;&#133;&yen;&eacute;&#150;&#128;&atilde;&#128;&#130;</title></head>
<body>
		<h2>&ccedil;&#155;&reg;&aelig;&not;&iexcl;</h2>
		<ul>
<li>
<b>Step1.</b>&atilde;&#129;&#144;&atilde;&#129;&#144;&atilde;&#130;&#140;</li>
			<li>
<b>Step2.</b>&eacute;&nbsp;&#145;&aring;&frac14;&micro;&atilde;&#130;&#140;</li>
		</ul>
</body>
</html>

これは読み込んだときにおかしな変換をされているので、DOMDocument->loadHTML()のあとにmetaタグを追加して文字エンコードをしても意味がない。 これを防ぐにはあらかじめ日本語文字列を数値文字参照に変換しておく。

<?php
$html = <<<EOD
<html>
	<head>
		<title>誰でもわかるプログラミング入門。</title>
	</head>
	<body>
		<h2>目次</h2>
		<ul>
			<li><b>Step1.</b>ぐぐれ</li>
			<li><b>Step2.</b>頑張れ</li>
		</ul>
	</body>
</html>
EOD;

//日本語を数値文字参照に変換する(文字化け対策)
$html = mb_convert_encoding($html, 'HTML-ENTITIES', 'ASCII, JIS, UTF-8, EUC-JP, SJIS');

//HTMLをパースする
$doc = new DOMDocument();
$doc->loadHTML($html);

//HTMLを出力
echo $doc->saveHTML();
?>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd">
<html>
<head><title>&#35504;&#12391;&#12418;&#12431;&#12363;&#12427;&#12503;&#12525;&#12464;&#12521;&#12511;&#12531;&#12464;&#20837;&#38272;&#12290;</title></head>
<body>
		<h2>&#30446;&#27425;</h2>
		<ul>
<li>
<b>Step1.</b>&#12368;&#12368;&#12428;</li>
			<li>
<b>Step2.</b>&#38929;&#24373;&#12428;</li>
		</ul>
</body>
</html>

(※数値文字参照と日本語文字列の変換については『数値文字参照と日本語文字列の相互変換をする [PHP]』参照。)

日本語文字列が数値文字参照になってしまっているが、これは先ほどと違いブラウザで表示すればちゃんと読むことができる。 数値文字参照になってしまっているのが嫌ならば、metaタグを追加して文字エンコードを指定する。

<?php
$html = <<<EOD
<html>
	<head>
		<title>誰でもわかるプログラミング入門。</title>
	</head>
	<body>
		<h2>目次</h2>
		<ul>
			<li><b>Step1.</b>ぐぐれ</li>
			<li><b>Step2.</b>頑張れ</li>
		</ul>
	</body>
</html>
EOD;

//日本語を数値文字参照に変換する(文字化け対策)
$encode = mb_detect_encoding($html, 'ASCII, JIS, UTF-8, EUC-JP, SJIS');
$html = mb_convert_encoding($html, 'HTML-ENTITIES', $encode);

//HTMLをパースする
$doc = new DOMDocument();
$doc->loadHTML($html);

//出力時に数値文字参照が出力される対策
$metaList = $doc->getElementsByTagName('meta');
$find = false;
foreach($metaList as $meta){
	if(strtolower($meta->getAttribute('http-equiv')) == 'content-type' && $meta->hasAttribute('content')){
		$find = true;
		break;
	}
}
if(!$find){
	//metaタグを追加する
	$head = $doc->getElementsByTagName('head')->item(0);
	$meta = $doc->createElement('meta');
	$meta->setAttribute('http-equiv', 'Content-Type');
	$meta->setAttribute('content', "text/html; charset={$encode}");
	$head->insertBefore($meta, $head->firstChild);
}

//HTMLを出力
echo $doc->saveHTML();
?>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>誰でもわかるプログラミング入門。</title>
</head>
<body>
		<h2>目次</h2>
		<ul>
<li>
<b>Step1.</b>ぐぐれ</li>
			<li>
<b>Step2.</b>頑張れ</li>
		</ul>
</body>
</html>