1. 導入: なぜHTML/XML解析が重要なのか?
PHPバックエンド開発において、外部データとの連携は避けて通れない道です。特に、Webサイトから特定の情報を取得するWebスクレイピングや、XML形式のAPIレスポンスを解析する場面では、効率的かつ堅牢なデータ抽出が求められます。手動で正規表現を駆使して文字列解析を行うことも可能ですが、HTML/XMLの構造が少しでも変わるとすぐに破綻してしまいます。
そこで登場するのが、PHP標準のDOM拡張モジュールとXPathです。これらを活用することで、HTML/XMLドキュメントをオブジェクトツリーとして扱い、構造的な変更にも強い柔軟なデータ抽出を実現できます。本記事では、実務で役立つDOMDocumentとXPathを使ったHTML/XML解析の基礎から応用までを、具体的なコード例を交えて解説します。
2. 基礎知識: DOMDocumentとXPathの基本
DOMDocumentとは?
DOMDocumentは、HTMLやXMLドキュメントを「Document Object Model (DOM)」の形式で表現するPHPのクラスです。DOMは、ドキュメント全体をツリー構造のオブジェクトとしてメモリ上に展開し、各要素、属性、テキストなどをノードとして扱います。これにより、プログラマはツリーを辿りながら、特定のノードの検索、追加、削除、変更といった操作をオブジェクト指向的に行えるようになります。
XPathとは?
XPath(XML Path Language)は、XMLドキュメント(HTMLも含む)内のノードを検索するための言語です。ファイルシステムにおけるパス指定のように、特定の要素や属性を効率的に指定できます。DOMDocumentと組み合わせることで、複雑な条件でのノード検索を非常に簡潔に記述できるため、データ抽出において強力なツールとなります。
基本的なXPathの記法
- `/` (ルートからの絶対パス): ドキュメントのルート要素からのパスを指定します。例: `/html/body/div`
- `//` (任意の場所からのパス): ドキュメント内の任意の場所から、指定したノードを検索します。例: `//div` (すべての `div` 要素)
- `@` (属性): ノードの属性を指定します。例: `//a/@href` (すべての `a` 要素の `href` 属性)
- `[]` (条件): 検索するノードに条件を付与します。例: `//div[@id=”content”]` (`id`属性が”content”の`div`要素)
- `text()` (テキストノード): ノードのテキスト内容を指定します。例: `//p[text()=”Hello”]` (テキストが”Hello”の`p`要素)
- `contains(@attr, ‘value’)` (部分一致): 属性値が特定の部分文字列を含むノードを検索します。例: `//img[contains(@src, ‘thumbnail’)]`
- `starts-with(@attr, ‘value’)` (前方一致): 属性値が特定の文字列で始まるノードを検索します。例: `//a[starts-with(@href, ‘http’)]`
- `.` (現在のノード): コンテキストノード(現在の検索起点となるノード)を表します。
3. 実装/解決策: DOMDocumentとXPathによるデータ抽出
HTML/XMLの解析は、以下のステップで進めます。
1. DOMDocumentオブジェクトの初期化: `new DOMDocument()` でインスタンスを作成します。
2. HTML/XMLの読み込み:
- 文字列からの読み込み: `loadHTML($htmlString)` または `loadXML($xmlString)`
- ファイルからの読み込み: `loadHTMLFile($filePath)` または `load($filePath)`
HTMLの場合、`mb_convert_encoding()` で文字エンコーディングを適切に処理し、`@`演算子で不正なHTMLによるWarningを抑制するのが一般的です。
3. DOMXPathオブジェクトの作成: `new DOMXPath($domDocument)` でインスタンスを作成し、XPathクエリを実行できるようにします。
4. XPathクエリの実行: `DOMXPath`オブジェクトの `query($xpathQuery, $contextNode)` メソッドを使用します。
- `$xpathQuery`: 検索条件を記述したXPath文字列。
- `$contextNode` (オプション): 検索の起点となるDOMノード。これを指定すると、そのノード以下から相対的に検索します。
`query()` メソッドは、条件に一致したすべてのノードを含む `DOMNodeList` オブジェクトを返します。
5. 結果の処理: `DOMNodeList` から `item($index)` メソッドで個々の `DOMNode` を取得し、`nodeValue` でテキスト内容、`getAttribute($attrName)` で属性値などを取り出します。
XPath内でPHP関数を利用する
XPath 1.0では、文字列の大文字小文字を無視した比較など、一部の高度な処理が直接できません。しかし、PHPのDOMXPathは、PHP関数をXPathクエリ内で利用できる機能を提供します。
- `$xpath->registerNamespace(“php”, “http://php.net/xpath”);`:`php`という名前空間を登録します。
- `$xpath->registerPHPFunctions();`:すべてのPHP関数をXPath内で利用できるようにします。特定の関数のみを登録することも可能です(例: `registerPHPFunctions(‘strcasecmp’)`)。
これにより、`php:functionString(‘strcasecmp’, name(), ‘dl’)=0` のように、XPathクエリ内でPHPの `strcasecmp` 関数を呼び出し、要素名の大文字小文字を無視した比較ができるようになります。
4. サンプルプログラム
以下のサンプルコードは、与えられたHTML文字列から様々な情報をXPathを使って抽出する方法を示しています。
/
function getInnerHtml(DOMNode $node): string
{
$html = ”;
// 子ノードを全て走査し、それぞれのHTMLを結合
foreach ($node->childNodes as $child) {
$html .= $node->ownerDocument->saveHTML($child);
}
return $html;
}
// 解析対象のHTML文字列
$html = <<
これはヘッダーです
"Hello, World!"
サンプル テキスト
- 項目A
- 項目B
- 項目C
- 用語
- 説明