[PHP]tidy_get_body関数でHTMLのbody要素をtidyNodeとして取得する方法を解説

PHP

PHPのTidy拡張でHTMLを処理するとき、「ドキュメント全体ではなく <body> タグの中身だけが欲しい」という場面はよくあります。そのために使うのが tidy_get_body() 関数です。

この関数は整形済みのHTMLから <body> 要素に対応する tidyNode オブジェクト を返します。文字列ではなくノードオブジェクトとして返ってくる点が特徴で、DOMツリーをたどる処理や、body 部分のみのHTML文字列の抽出に活用できます。この記事では基本仕様から実践的な活用パターンまで詳しく解説します。

関数概要

項目内容
関数名tidy_get_body()
読み方タイディ・ゲット・ボディ
分類Tidy関数
対応バージョンPHP 5以降(Tidy拡張が有効な場合)
引数tidyオブジェクト
戻り値tidyNode(body要素のノード)
必要な拡張tidy(多くの環境で標準バンドル)
OOPスタイル$tidy->body()

構文

// 手続き型
tidy_get_body(tidy $tidy): tidyNode

// オブジェクト指向型(推奨)
$tidy->body(): tidyNode

返却される tidyNode オブジェクトは <body> 要素そのものを表すノードで、次のような主要プロパティを持ちます。

プロパティ内容
$node->namestringノード名("body" など)
$node->valuestringノードの内部HTML文字列
$node->typeintノードの種類(TIDY_NODETYPE_* 定数)
$node->childarray|null子ノードの配列
$node->attributearray|null属性の連想配列
$node->isHTML()boolHTMLノードかどうか
$node->hasChildren()bool子ノードを持つかどうか

show-body-only 設定との違い

<body> 部分だけのHTML文字列が欲しい場合、設定オプション show-body-only => true を使う方法もあります。両者の使い分けを整理します。

【show-body-only => true を設定した場合】
(string) $tidy → "<body> タグを含まない body の中身の文字列" を返す

【tidy_get_body() を使う場合】
$node = tidy_get_body($tidy)
$node->value → "<body> タグを含む body 全体の文字列" を返す
$node->child → 子ノードのリストとして操作できる

「ただ <body> の中身を文字列として取り出したい」だけなら show-body-only が手軽です。DOMツリーとして操作・探索したい場合は tidy_get_body() が適しています。

処理の流れ

tidy_parse_string() / tidy_parse_file()
        │
        ▼
  tidy_clean_repair()
        │
        ▼
  tidy_get_body()
        │
        ▼
  tidyNode(body要素)
        │
        ├─ $node->value    → body の HTML 文字列
        ├─ $node->child    → 子ノードの配列
        ├─ $node->name     → "body"
        └─ $node->attribute → id, class などの属性

基本的な使い方

<?php
$html = '<html><head><title>テスト</title></head><body><h1>見出し</h1><p>本文</p></body></html>';

$tidy = tidy_parse_string($html, [], 'UTF8');
tidy_clean_repair($tidy);

$body = tidy_get_body($tidy);

echo "ノード名: " . $body->name . PHP_EOL;
echo "---" . PHP_EOL;
echo $body->value . PHP_EOL;

実行結果:

ノード名: body
---
<body>
<h1>見出し</h1>
<p>本文</p>
</body>

$body->value には <body> タグ自体を含む形でHTML文字列が入ります。<body> タグを除いた中身だけが必要なら、後述の正規表現や show-body-only 設定を使います。

実践的なコード例

例1:body の中身だけをHTML文字列として取り出す

<?php
class BodyExtractor
{
    public function extract(string $fullHtml): string
    {
        $tidy = tidy_parse_string($fullHtml, [
            'output-xhtml' => true,
            'wrap'         => 0,
        ], 'UTF8');
        tidy_clean_repair($tidy);

        $bodyNode = tidy_get_body($tidy);

        // $bodyNode->value は <body>...</body> を含むので
        // 最外周の <body> タグを除去して中身だけを返す
        $html = $bodyNode->value;
        $html = preg_replace('/^<body[^>]*>/i', '', $html);
        $html = preg_replace('/<\/body>$/i', '', $html);

        return trim($html);
    }
}

$extractor = new BodyExtractor();

$full = <<<HTML
<!DOCTYPE html>
<html>
<head><title>ページタイトル</title></head>
<body>
  <h1>見出し</h1>
  <p>本文テキスト</p>
</body>
</html>
HTML;

echo $extractor->extract($full);

実行結果:

<h1>見出し</h1>
<p>本文テキスト</p>

例2:子ノードを再帰的に走査してテキストノードを収集する

<?php
class TextNodeCollector
{
    private array $texts = [];

    public function collect(string $html): array
    {
        $tidy = tidy_parse_string($html, [], 'UTF8');
        tidy_clean_repair($tidy);

        $this->texts = [];
        $this->traverse(tidy_get_body($tidy));

        return $this->texts;
    }

    private function traverse(tidyNode $node): void
    {
        // テキストノード(TIDY_NODETYPE_TEXT = 3)を収集
        if ($node->type === TIDY_NODETYPE_TEXT) {
            $text = trim($node->value);
            if ($text !== '') {
                $this->texts[] = $text;
            }
        }

        if ($node->hasChildren()) {
            foreach ($node->child as $child) {
                $this->traverse($child);
            }
        }
    }
}

$collector = new TextNodeCollector();
$texts = $collector->collect(
    '<html><body><h1>タイトル</h1><p>段落1</p><p>段落2</p></body></html>'
);

print_r($texts);

実行結果:

Array
(
    [0] => タイトル
    [1] => 段落1
    [2] => 段落2
)

tidy_get_body() で取得した tidyNode を起点に child をたどることで、DOMツリーを再帰的に走査してテキストコンテンツだけを抽出できます。

例3:body直下の要素名一覧を取得する

<?php
class DirectChildTagLister
{
    public function list(string $html): array
    {
        $tidy = tidy_parse_string($html, [], 'UTF8');
        tidy_clean_repair($tidy);

        $body = tidy_get_body($tidy);
        $tags = [];

        if ($body->hasChildren()) {
            foreach ($body->child as $child) {
                // HTMLノードのみ(テキストノードを除外)
                if ($child->type === TIDY_NODETYPE_START || $child->type === TIDY_NODETYPE_STARTEND) {
                    $tags[] = $child->name;
                }
            }
        }

        return $tags;
    }
}

$lister = new DirectChildTagLister();
$tags = $lister->list(
    '<html><body><header>ヘッダー</header><main><p>本文</p></main><footer>フッター</footer></body></html>'
);

print_r($tags);

実行結果:

Array
(
    [0] => header
    [1] => main
    [2] => footer
)

例4:OOPスタイル(body())での記述

<?php
class OopBodyNodeDemo
{
    private tidy $tidy;

    public function parse(string $html): self
    {
        $this->tidy = new tidy();
        $this->tidy->parseString($html, [
            'output-xhtml' => true,
            'wrap'         => 0,
        ], 'UTF8');
        $this->tidy->cleanRepair();
        return $this;
    }

    public function getBodyInfo(): array
    {
        // OOPスタイルの body() は tidy_get_body() と同等
        $body = $this->tidy->body();

        return [
            'name'         => $body->name,
            'has_children' => $body->hasChildren(),
            'child_count'  => $body->hasChildren() ? count($body->child) : 0,
        ];
    }
}

$demo = new OopBodyNodeDemo();
$info = $demo->parse('<html><body><p>A</p><p>B</p><p>C</p></body></html>')
             ->getBodyInfo();

print_r($info);

実行結果:

Array
(
    [name] => body
    [has_children] => 1
    [child_count] => 3
)

例5:body の属性(class・id)を取得する

<?php
class BodyAttributeReader
{
    public function read(string $html): array
    {
        $tidy = tidy_parse_string($html, [], 'UTF8');
        tidy_clean_repair($tidy);

        $body       = tidy_get_body($tidy);
        $attributes = $body->attribute ?? [];

        return $attributes;
    }
}

$reader = new BodyAttributeReader();

$attrs = $reader->read(
    '<html><body class="home dark-mode" id="main-body"><p>本文</p></body></html>'
);

print_r($attrs);

実行結果:

Array
(
    [class] => home dark-mode
    [id] => main-body
)

$body->attribute は属性名をキー、属性値を値とする連想配列で、<body> タグに付いた classid などを取得できます。

例6:特定のタグを body ノードから検索する

<?php
class TagFinder
{
    private array $found = [];

    public function find(string $html, string $tagName): array
    {
        $tidy = tidy_parse_string($html, [], 'UTF8');
        tidy_clean_repair($tidy);

        $this->found = [];
        $this->search(tidy_get_body($tidy), strtolower($tagName));

        return $this->found;
    }

    private function search(tidyNode $node, string $tag): void
    {
        if (strtolower($node->name ?? '') === $tag) {
            $this->found[] = $node->value;
        }

        if ($node->hasChildren()) {
            foreach ($node->child as $child) {
                $this->search($child, $tag);
            }
        }
    }
}

$finder = new TagFinder();
$links  = $finder->find(
    '<html><body><p><a href="/top">トップ</a></p><p><a href="/about">会社概要</a></p></body></html>',
    'a'
);

foreach ($links as $link) {
    echo $link . PHP_EOL;
}

実行結果:

<a href="/top">トップ</a>
<a href="/about">会社概要</a>

例7:body ノードの存在チェックつき安全な呼び出し

<?php
class SafeBodyNodeAccessor
{
    public function getBodyValue(string $html): ?string
    {
        if (!extension_loaded('tidy')) {
            trigger_error('tidy拡張が利用できません。', E_USER_WARNING);
            return null;
        }

        $tidy = tidy_parse_string($html, ['wrap' => 0], 'UTF8');
        tidy_clean_repair($tidy);

        $body = tidy_get_body($tidy);

        if (!isset($body->value)) {
            return null;
        }

        return $body->value;
    }
}

$accessor = new SafeBodyNodeAccessor();
$value    = $accessor->getBodyValue('<html><body><p>安全なアクセス</p></body></html>');

echo $value ?? '取得できませんでした';

実行結果:

<body>
<p>安全なアクセス</p>
</body>

関連関数との比較

関数OOPスタイル返却する要素
tidy_get_body()body()<body> 要素の tidyNode
tidy_get_head()head()<head> 要素の tidyNode
tidy_get_html()html()<html> 要素の tidyNode
tidy_get_root()root()ドキュメントルートの tidyNode
tidy_get_output()value プロパティドキュメント全体のHTML文字列
show-body-only 設定<body> 内の中身だけのHTML文字列(タグなし)

よくある落とし穴・注意点

  1. $node->value には <body> タグ自体が含まれる tidy_get_body() が返す tidyNodevalue プロパティは <body>...</body> 全体を含みます。<body> タグを除いた中身だけが欲しい場合は、show-body-only => true 設定を使うか、正規表現で外周タグを除去する処理を加えてください。
  2. tidy_clean_repair() を呼ばないとノードが不完全 tidy_get_body()cleanRepair() 実行後のHTMLに基づいてノードを構築します。cleanRepair() を省略すると、修正前の不完全なHTMLに基づくノードが返される場合があります。必ず parse → cleanRepair → get_body の順で呼び出してください。
  3. $node->childnull になる場合がある <body> 要素が空、またはテキストノードのみで構成されている場合、$node->childnull になることがあります。$node->hasChildren() で子ノードの存在を確認してからアクセスしましょう。
  4. フラグメント(body タグなし)を渡すと Tidy が補完する <body> タグのないHTMLフラグメントを tidy_parse_string() に渡しても、Tidyが自動で <body> を補完するため、tidy_get_body() は常に有効なノードを返します。ただし補完されたHTMLが意図通りかどうかは errorBuffer で確認しましょう。
  5. tidyNodeDOMNode とは別物 PHP の DOMDocument クラスが扱う DOMNode とは異なるオブジェクトです。Tidy の tidyNode を DOMDocument のメソッドで操作しようとするとエラーになります。DOMDocument での高度な操作が必要な場合は、tidy_get_body() で取得したHTML文字列をいったん DOMDocument::loadHTML() に渡して変換する方法を検討してください。

まとめ

ポイント内容
役割HTMLの <body> 要素を tidyNode オブジェクトとして取得する
引数tidyオブジェクト
戻り値tidyNode(body要素のノード)
OOPスタイル$tidy->body()
$node->value<body> タグを含む body 全体のHTML文字列
よく組み合わせる関数・プロパティtidy_clean_repair(), $node->child, $node->hasChildren()
show-body-only との違いノード操作が可能か(tidy_get_body)vs 文字列だけ欲しい(show-body-only)
主な用途body部分の抽出、DOMツリー走査、テキスト・タグの収集、属性の取得
注意点value<body> タグが含まれる、child は null チェックが必要

tidy_get_body() は「HTMLの <body> 部分をDOMノードとして扱いたい」場面で力を発揮します。単純な文字列抽出なら show-body-only 設定のほうが手軽ですが、子ノードの走査・属性の取得・タグの検索など、ツリー構造を活かした処理が必要なときは tidy_get_body() が適しています。

タイトルとURLをコピーしました