1. 関数概要
tidy_get_root は、PHP の Tidy 拡張が解析した HTML/XHTML ドキュメントの DOM ツリーのルートノードを tidyNode オブジェクトとして返す関数です。返された tidyNode を起点にして子ノードを再帰的に辿ることで、HTML 構造全体をプログラムで走査・検査・抽出できます。
| 項目 | 内容 |
|---|---|
| 関数名 | tidy_get_root |
| 所属拡張 | Tidy |
| 戻り値の型 | tidyNode|false |
| 手続き型 / OOP | 手続き型(OOP 版: $tidy->root()) |
| PHP バージョン | PHP 5 以降 |
| 公式ドキュメント | https://www.php.net/manual/ja/tidy.root.php |
2. 構文
// 手続き型
tidy_get_root(tidy $tidy): tidyNode|false
// オブジェクト指向型
$tidy->root(): tidyNode|false
パラメータ
| パラメータ | 型 | 説明 |
|---|---|---|
$tidy | tidy | tidy_parse_string() 等で生成した tidy オブジェクト |
戻り値
- 成功時: DOM ツリーの最上位を表す
tidyNodeオブジェクト - 失敗時:
false
3. tidyNode の主なプロパティ
tidy_get_root() が返す tidyNode オブジェクトは以下のプロパティを持ちます。
| プロパティ | 型 | 内容 |
|---|---|---|
$node->name | string | ノード名(タグ名、例: "html", "body", "p" など) |
$node->value | string|null | テキストノードの場合の文字列値 |
$node->type | int | ノードの種別(下表参照) |
$node->id | int | HTML タグの ID(TIDY_TAG_* 定数に対応) |
$node->attribute | array|null | 属性の連想配列(例: ["href" => "https://..."]) |
$node->hasChildren() | bool | 子ノードを持つかどうか |
$node->hasSiblings() | bool | 兄弟ノードを持つかどうか |
$node->child | array|null | 子 tidyNode の配列 |
ノード種別($node->type)
| 定数 | 値 | 意味 |
|---|---|---|
TIDY_NODETYPE_ROOT | 0 | ルートノード |
TIDY_NODETYPE_DOCTYPE | 1 | DOCTYPE 宣言 |
TIDY_NODETYPE_COMMENT | 2 | コメントノード |
TIDY_NODETYPE_PROCINS | 3 | 処理命令 |
TIDY_NODETYPE_TEXT | 4 | テキストノード |
TIDY_NODETYPE_START | 5 | 開始タグ |
TIDY_NODETYPE_END | 6 | 終了タグ |
TIDY_NODETYPE_STARTEND | 7 | 空要素タグ(<br /> 等) |
TIDY_NODETYPE_CDATA | 8 | CDATA セクション |
TIDY_NODETYPE_SECTION | 9 | XML セクション |
TIDY_NODETYPE_ASP | 10 | ASP ノード |
TIDY_NODETYPE_JSTE | 11 | JSTE ノード |
TIDY_NODETYPE_PHP | 12 | PHP ノード |
TIDY_NODETYPE_XMLDECL | 13 | XML 宣言 |
4. 動作概念図
tidy_get_root($tidy)
│
▼
┌──────────────────────────────────────────────┐
│ tidyNode (type=ROOT, name="") │ ← ルートノード
│ │
│ ├── tidyNode (type=DOCTYPE) │ DOCTYPE 宣言
│ │ │
│ └── tidyNode (type=START, name="html") │ <html>
│ │ │
│ ├── tidyNode (name="head") │ <head>
│ │ └── tidyNode (name="title") │ <title>
│ │ └── tidyNode (type=TEXT) │ テキスト
│ │ │
│ └── tidyNode (name="body") │ <body>
│ ├── tidyNode (name="h1") │ <h1>
│ │ └── tidyNode (type=TEXT) │ テキスト
│ └── tidyNode (name="p") │ <p>
│ └── tidyNode (type=TEXT) │ テキスト
└──────────────────────────────────────────────┘
hasChildren() / $node->child[] で子ノードへ再帰的に辿れる
5. 基本的な使い方
<?php
$html = '<!DOCTYPE html>
<html>
<head><title>サンプル</title></head>
<body><h1>見出し</h1><p>本文テキスト</p></body>
</html>';
$tidy = tidy_parse_string($html, [], 'UTF8');
tidy_clean_repair($tidy);
$root = tidy_get_root($tidy);
if ($root !== false) {
echo "ルートノード名 : " . ($root->name ?? '(空)') . PHP_EOL;
echo "ノード種別 : " . $root->type . PHP_EOL;
echo "子ノード有無 : " . ($root->hasChildren() ? 'あり' : 'なし') . PHP_EOL;
}
出力例:
ルートノード名 : (空)
ノード種別 : 0
子ノード有無 : あり
6. 実践的なコード例
例1: DOM ツリーを再帰的に表示するクラス
<?php
class TidyTreePrinter
{
public function print(tidy $tidy): void
{
$root = tidy_get_root($tidy);
if ($root === false) {
echo "ルートノードの取得に失敗しました。" . PHP_EOL;
return;
}
$this->walk($root, 0);
}
private function walk(tidyNode $node, int $depth): void
{
$indent = str_repeat(' ', $depth);
$label = match($node->type) {
TIDY_NODETYPE_ROOT => '[ROOT]',
TIDY_NODETYPE_DOCTYPE => '[DOCTYPE]',
TIDY_NODETYPE_TEXT => '[TEXT] ' . trim($node->value ?? ''),
TIDY_NODETYPE_START,
TIDY_NODETYPE_STARTEND => '<' . $node->name . '>',
default => '[type=' . $node->type . ']',
};
echo $indent . $label . PHP_EOL;
if ($node->hasChildren()) {
foreach ($node->child as $child) {
$this->walk($child, $depth + 1);
}
}
}
}
$html = '<!DOCTYPE html><html><head><title>テスト</title></head>
<body><h1>見出し</h1><p>本文</p></body></html>';
$tidy = tidy_parse_string($html, [], 'UTF8');
tidy_clean_repair($tidy);
$printer = new TidyTreePrinter();
$printer->print($tidy);
出力例:
[ROOT]
[DOCTYPE]
<html>
<head>
<title>
[TEXT] テスト
<body>
<h1>
[TEXT] 見出し
<p>
[TEXT] 本文
例2: OOP スタイルで $tidy->root() を使う
<?php
class TidyOopRootFetcher
{
private tidy $tidy;
public function __construct(string $html, array $config = [])
{
$this->tidy = new tidy();
$this->tidy->parseString($html, $config, 'UTF8');
$this->tidy->cleanRepair();
}
public function getRoot(): tidyNode|false
{
return $this->tidy->root();
}
public function describeRoot(): void
{
$root = $this->getRoot();
if ($root === false) {
echo "取得失敗" . PHP_EOL;
return;
}
echo "ノード種別 : " . $root->type . " (ROOT=" . TIDY_NODETYPE_ROOT . ")" . PHP_EOL;
echo "子ノード数 : " . count($root->child ?? []) . PHP_EOL;
}
}
$html = '<!DOCTYPE html><html><head><title>OOP</title></head><body><p>本文</p></body></html>';
$fetcher = new TidyOopRootFetcher($html);
$fetcher->describeRoot();
出力例:
ノード種別 : 0 (ROOT=0)
子ノード数 : 2
例3: 特定のタグ名を持つノードをすべて収集するクラス
<?php
class TidyNodeCollector
{
/** @var tidyNode[] */
private array $found = [];
public function collect(tidy $tidy, string $tagName): array
{
$this->found = [];
$root = tidy_get_root($tidy);
if ($root !== false) {
$this->search($root, strtolower($tagName));
}
return $this->found;
}
private function search(tidyNode $node, string $tagName): void
{
if (strtolower($node->name ?? '') === $tagName) {
$this->found[] = $node;
}
if ($node->hasChildren()) {
foreach ($node->child as $child) {
$this->search($child, $tagName);
}
}
}
}
$html = '<!DOCTYPE html><html><body>
<p>段落1</p><p>段落2</p><div><p>段落3</p></div>
</body></html>';
$tidy = tidy_parse_string($html, [], 'UTF8');
tidy_clean_repair($tidy);
$collector = new TidyNodeCollector();
$pNodes = $collector->collect($tidy, 'p');
echo "見つかった <p> タグの数: " . count($pNodes) . PHP_EOL;
foreach ($pNodes as $i => $node) {
$text = '';
if ($node->hasChildren()) {
foreach ($node->child as $child) {
if ($child->type === TIDY_NODETYPE_TEXT) {
$text .= trim($child->value ?? '');
}
}
}
echo " [{$i}] " . $text . PHP_EOL;
}
出力例:
見つかった <p> タグの数: 3
[0] 段落1
[1] 段落2
[2] 段落3
例4: すべてのリンク(href)を抽出するクラス
<?php
class TidyLinkExtractor
{
/** @var string[] */
private array $links = [];
public function extract(tidy $tidy): array
{
$this->links = [];
$root = tidy_get_root($tidy);
if ($root !== false) {
$this->walk($root);
}
return array_unique($this->links);
}
private function walk(tidyNode $node): void
{
if (strtolower($node->name ?? '') === 'a') {
$href = $node->attribute['href'] ?? null;
if ($href !== null && $href !== '') {
$this->links[] = $href;
}
}
if ($node->hasChildren()) {
foreach ($node->child as $child) {
$this->walk($child);
}
}
}
}
$html = '<!DOCTYPE html><html><body>
<a href="https://example.com">Example</a>
<a href="https://php.net">PHP</a>
<a href="https://example.com">Example 重複</a>
<p>リンクなし</p>
</body></html>';
$tidy = tidy_parse_string($html, [], 'UTF8');
tidy_clean_repair($tidy);
$extractor = new TidyLinkExtractor();
$links = $extractor->extract($tidy);
echo "抽出されたリンク一覧:" . PHP_EOL;
foreach ($links as $link) {
echo " - " . $link . PHP_EOL;
}
出力例:
抽出されたリンク一覧:
- https://example.com
- https://php.net
例5: テキストノードの内容をすべて結合して取得するクラス
<?php
class TidyTextExtractor
{
private string $buffer = '';
public function extractText(tidy $tidy): string
{
$this->buffer = '';
$root = tidy_get_root($tidy);
if ($root !== false) {
$this->walk($root);
}
return trim($this->buffer);
}
private function walk(tidyNode $node): void
{
if ($node->type === TIDY_NODETYPE_TEXT) {
$this->buffer .= $node->value ?? '';
}
if ($node->hasChildren()) {
foreach ($node->child as $child) {
$this->walk($child);
}
}
}
}
$html = '<!DOCTYPE html><html><head><title>タイトル</title></head>
<body><h1>見出し</h1><p>段落のテキスト。</p><p>2つ目の段落。</p></body></html>';
$tidy = tidy_parse_string($html, [], 'UTF8');
tidy_clean_repair($tidy);
$extractor = new TidyTextExtractor();
echo "抽出テキスト:" . PHP_EOL;
echo $extractor->extractText($tidy) . PHP_EOL;
出力例:
抽出テキスト:
タイトル
見出し
段落のテキスト。
2つ目の段落。
例6: ノード数・深さ・タグ種別を集計するクラス
<?php
class TidyTreeAnalyzer
{
private int $totalNodes = 0;
private int $maxDepth = 0;
/** @var array<string, int> */
private array $tagCounts = [];
public function analyze(tidy $tidy): void
{
$this->totalNodes = 0;
$this->maxDepth = 0;
$this->tagCounts = [];
$root = tidy_get_root($tidy);
if ($root !== false) {
$this->walk($root, 0);
}
}
private function walk(tidyNode $node, int $depth): void
{
$this->totalNodes++;
$this->maxDepth = max($this->maxDepth, $depth);
$name = $node->name ?? '';
if ($name !== '' && $node->type === TIDY_NODETYPE_START) {
$this->tagCounts[$name] = ($this->tagCounts[$name] ?? 0) + 1;
}
if ($node->hasChildren()) {
foreach ($node->child as $child) {
$this->walk($child, $depth + 1);
}
}
}
public function report(): void
{
echo "=== DOM ツリー解析レポート ===" . PHP_EOL;
echo "総ノード数 : " . $this->totalNodes . PHP_EOL;
echo "最大深さ : " . $this->maxDepth . PHP_EOL;
echo "タグ別集計 :" . PHP_EOL;
arsort($this->tagCounts);
foreach ($this->tagCounts as $tag => $count) {
printf(" %-15s: %d 個%s", "<{$tag}>", $count, PHP_EOL);
}
}
}
$html = '<!DOCTYPE html><html><head><title>解析</title></head>
<body>
<h1>見出し</h1>
<p>段落1</p><p>段落2</p>
<ul><li>項目1</li><li>項目2</li><li>項目3</li></ul>
<a href="#">リンク</a>
</body></html>';
$tidy = tidy_parse_string($html, [], 'UTF8');
tidy_clean_repair($tidy);
$analyzer = new TidyTreeAnalyzer();
$analyzer->analyze($tidy);
$analyzer->report();
出力例:
=== DOM ツリー解析レポート ===
総ノード数 : 28
最大深さ : 5
タグ別集計 :
<li> : 3 個
<p> : 2 個
<html> : 1 個
<head> : 1 個
<title> : 1 個
<body> : 1 個
<h1> : 1 個
<ul> : 1 個
<a> : 1 個
例7: 属性値でノードを検索するクラス
<?php
class TidyAttributeSearcher
{
/** @var tidyNode[] */
private array $results = [];
/**
* 指定した属性名と値(部分一致)でノードを検索する
*/
public function search(tidy $tidy, string $attrName, string $attrValue = ''): array
{
$this->results = [];
$root = tidy_get_root($tidy);
if ($root !== false) {
$this->walk($root, $attrName, $attrValue);
}
return $this->results;
}
private function walk(tidyNode $node, string $attrName, string $attrValue): void
{
$attrs = $node->attribute ?? [];
if (isset($attrs[$attrName])) {
if ($attrValue === '' || str_contains($attrs[$attrName], $attrValue)) {
$this->results[] = $node;
}
}
if ($node->hasChildren()) {
foreach ($node->child as $child) {
$this->walk($child, $attrName, $attrValue);
}
}
}
}
$html = '<!DOCTYPE html><html><body>
<div class="container main">メインコンテナ</div>
<div class="sidebar">サイドバー</div>
<p class="container sub">サブ段落</p>
<a href="https://example.com" class="link">リンク</a>
</body></html>';
$tidy = tidy_parse_string($html, [], 'UTF8');
tidy_clean_repair($tidy);
$searcher = new TidyAttributeSearcher();
// class 属性に "container" を含むノードを検索
$nodes = $searcher->search($tidy, 'class', 'container');
echo "class に 'container' を含むノード: " . count($nodes) . " 件" . PHP_EOL;
foreach ($nodes as $node) {
echo " <" . $node->name . " class=\"" . ($node->attribute['class'] ?? '') . "\">" . PHP_EOL;
}
出力例:
class に 'container' を含むノード: 2 件
<div class="container main">
<p class="container sub">
7. 関連関数との比較
| 関数名 | 取得対象 | 戻り値 |
|---|---|---|
tidy_get_root() | DOM ツリー全体のルートノード | tidyNode|false |
tidy_get_html() | <html> 要素ノード | tidyNode|false |
tidy_get_head() | <head> 要素ノード | tidyNode|false |
tidy_get_body() | <body> 要素ノード | tidyNode|false |
tidy_get_output() | 整形済み HTML 文字列 | string |
tidy_get_error_buffer() | エラーバッファ文字列 | string|false |
tidy_get_status() | 解析ステータス | int |
使い分けのポイント:
<body>以下のコンテンツだけ走査したい場合はtidy_get_body()、DOCTYPE を含む文書全体を走査したい場合はtidy_get_root()を使います。
8. よくある落とし穴と注意点
① tidy_clean_repair() を呼ばなくてもノードは取得できるが修復はされない
tidy_get_root() 自体は tidy_clean_repair() なしでも動作しますが、不正な HTML の場合にノード構造が不完全になることがあります。確実な走査のためにクリーニング後に呼び出してください。
// 推奨: cleanRepair 後に root を取得
$tidy = tidy_parse_string($html, [], 'UTF8');
tidy_clean_repair($tidy);
$root = tidy_get_root($tidy);
② ルートノードは <html> タグではない
tidy_get_root() が返すノードは type=0(TIDY_NODETYPE_ROOT)の仮想的なルートであり、<html> 要素そのものではありません。<html> 要素を直接取得したい場合は tidy_get_html() を使います。
$root = tidy_get_root($tidy); // 仮想ルート(name は空文字)
$htmlTag = tidy_get_html($tidy); // <html> 要素ノード
③ テキストノードの name は空文字になる
テキストノード(type=TIDY_NODETYPE_TEXT)は name プロパティが空文字または null になります。$node->name で分岐する際は type も合わせて確認してください。
④ 再帰が深いと stack overflow に注意
非常に深くネストした HTML を処理する場合、再帰的な走査でスタックオーバーフローが起きることがあります。ini_set('xdebug.max_nesting_level', 500) の調整や、スタックを使った反復処理への置き換えを検討してください。
9. まとめ
| 項目 | 内容 |
|---|---|
| 主な用途 | Tidy が解析した DOM ツリーのルートノードを取得し再帰走査の起点とする |
| 戻り値 | tidyNode|false |
| ルートノードの種別 | TIDY_NODETYPE_ROOT(=0)、<html> タグ自体ではない |
| OOP 版 | $tidy->root() |
| 子ノードへのアクセス | $node->child 配列 / $node->hasChildren() |
| よく使う組み合わせ | tidy_get_body(), tidy_get_head(), tidy_get_html(), tidy_clean_repair() |
tidy_get_root() は Tidy の DOM 走査機能の入口となる関数です。返された tidyNode を再帰的に辿ることで、リンク抽出・テキスト収集・タグ集計・属性検索など、HTML 構造に基づいた多様な処理を実装できます。シンプルなコンテンツ抽出には tidy_get_body() との組み合わせも効果的です。
