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

PHP

PHPのTidy拡張でHTMLを処理するとき、<meta> タグの内容確認や <title> の抽出、<link> 要素の一覧取得など、<head> 要素の中身だけを操作したい場面があります。そのときに使うのが tidy_get_head() です。

tidy_get_body()<head> 版にあたる関数で、整形済みHTMLの <head> 要素に対応する tidyNode オブジェクト を返します。DOMツリーとして子ノードを走査でき、メタ情報の読み取りや検証に役立ちます。

関数概要

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

構文

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

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

返却される tidyNode の主要プロパティは tidy_get_body() と共通です。

プロパティ / メソッド内容
$node->nameノード名("head"
$node->value<head>...</head> を含むHTML文字列
$node->child子ノードの配列(null の場合あり)
$node->attribute属性の連想配列
$node->hasChildren()子ノードを持つかどうか
$node->typeノード種別(TIDY_NODETYPE_* 定数)

基本的な使い方

<?php
$html = <<<HTML
<html>
<head>
  <meta charset="UTF-8">
  <title>サンプルページ</title>
  <link rel="stylesheet" href="style.css">
</head>
<body><p>本文</p></body>
</html>
HTML;

$tidy = tidy_parse_string($html, ['output-xhtml' => true], 'UTF8');
tidy_clean_repair($tidy);

$head = tidy_get_head($tidy);

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

実行結果:

ノード名: head
---
<head>
<meta charset="UTF-8">
<title>サンプルページ</title>
<link rel="stylesheet" href="style.css">
</head>

$head->value には <head> タグ自体を含む形でHTML文字列が返ります。

処理の流れ

tidy_parse_string() / tidy_parse_file()
        │
        ▼
  tidy_clean_repair()
        │
        ▼
  tidy_get_head()
        │
        ▼
  tidyNode(head要素)
        │
        ├─ $node->value    → <head>...</head> の文字列
        ├─ $node->child    → <title>, <meta>, <link> などの子ノード
        └─ $node->name     → "head"

実践的なコード例

例1:<title> タグの内容を取得する

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

        $head = tidy_get_head($tidy);

        if (!$head->hasChildren()) {
            return null;
        }

        foreach ($head->child as $child) {
            if (strtolower($child->name ?? '') === 'title') {
                // title の子がテキストノード
                if ($child->hasChildren()) {
                    return trim($child->child[0]->value ?? '');
                }
            }
        }

        return null;
    }
}

$extractor = new TitleExtractor();

$html = '<html><head><title>PHPプログラミング入門</title></head><body><p>本文</p></body></html>';
echo $extractor->extract($html) . PHP_EOL;

実行結果:

PHPプログラミング入門

例2:<meta> タグの属性を一覧取得する

<?php
class MetaTagCollector
{
    public function collect(string $html): array
    {
        $tidy = tidy_parse_string($html, ['output-xhtml' => true], 'UTF8');
        tidy_clean_repair($tidy);

        $head  = tidy_get_head($tidy);
        $metas = [];

        if (!$head->hasChildren()) {
            return $metas;
        }

        foreach ($head->child as $child) {
            if (strtolower($child->name ?? '') === 'meta') {
                $metas[] = $child->attribute ?? [];
            }
        }

        return $metas;
    }
}

$collector = new MetaTagCollector();

$html = <<<HTML
<html>
<head>
  <meta charset="UTF-8">
  <meta name="description" content="PHPのTidy拡張を解説するページです。">
  <meta name="keywords" content="PHP, Tidy, HTML">
  <meta property="og:title" content="PHP Tidy解説">
</head>
<body></body>
</html>
HTML;

$metas = $collector->collect($html);
foreach ($metas as $i => $attr) {
    echo "meta[{$i}]: ";
    foreach ($attr as $key => $val) {
        echo "{$key}=\"{$val}\" ";
    }
    echo PHP_EOL;
}

実行結果:

meta[0]: charset="UTF-8"
meta[1]: name="description" content="PHPのTidy拡張を解説するページです。"
meta[2]: name="keywords" content="PHP, Tidy, HTML"
meta[3]: property="og:title" content="PHP Tidy解説"

例3:<link> タグのスタイルシートURLを収集する

<?php
class StylesheetUrlCollector
{
    public function collect(string $html): array
    {
        $tidy = tidy_parse_string($html, ['output-xhtml' => true], 'UTF8');
        tidy_clean_repair($tidy);

        $head  = tidy_get_head($tidy);
        $urls  = [];

        if (!$head->hasChildren()) {
            return $urls;
        }

        foreach ($head->child as $child) {
            $name = strtolower($child->name ?? '');
            $attr = $child->attribute ?? [];

            if ($name === 'link'
                && isset($attr['rel'])
                && strtolower($attr['rel']) === 'stylesheet'
                && isset($attr['href'])
            ) {
                $urls[] = $attr['href'];
            }
        }

        return $urls;
    }
}

$collector = new StylesheetUrlCollector();

$html = <<<HTML
<html>
<head>
  <link rel="stylesheet" href="/css/base.css">
  <link rel="stylesheet" href="/css/layout.css">
  <link rel="icon" href="/favicon.ico">
</head>
<body></body>
</html>
HTML;

$stylesheets = $collector->collect($html);
foreach ($stylesheets as $url) {
    echo $url . PHP_EOL;
}

実行結果:

/css/base.css
/css/layout.css

rel="icon" は除外され、rel="stylesheet" のURLのみ取得できています。

例4:SEOチェック(title・descriptionの有無を確認する)

<?php
class SeoHeadChecker
{
    public function check(string $html): array
    {
        $tidy = tidy_parse_string($html, ['output-xhtml' => true], 'UTF8');
        tidy_clean_repair($tidy);

        $head   = tidy_get_head($tidy);
        $result = [
            'has_title'       => false,
            'title'           => null,
            'has_description' => false,
            'description'     => null,
            'has_charset'     => false,
            'charset'         => null,
        ];

        if (!$head->hasChildren()) {
            return $result;
        }

        foreach ($head->child as $child) {
            $name = strtolower($child->name ?? '');
            $attr = $child->attribute ?? [];

            if ($name === 'title' && $child->hasChildren()) {
                $result['has_title'] = true;
                $result['title']     = trim($child->child[0]->value ?? '');
            }

            if ($name === 'meta') {
                if (isset($attr['name']) && strtolower($attr['name']) === 'description') {
                    $result['has_description'] = true;
                    $result['description']     = $attr['content'] ?? '';
                }
                if (isset($attr['charset'])) {
                    $result['has_charset'] = true;
                    $result['charset']     = $attr['charset'];
                }
            }
        }

        return $result;
    }

    public function report(array $result): void
    {
        $checks = [
            'title'       => [$result['has_title'],       $result['title']],
            'description' => [$result['has_description'], $result['description']],
            'charset'     => [$result['has_charset'],     $result['charset']],
        ];

        foreach ($checks as $label => [$exists, $value]) {
            $icon = $exists ? '✓' : '✗';
            printf("[%s] %-15s %s\n", $icon, $label, $value ?? '(未設定)');
        }
    }
}

$checker = new SeoHeadChecker();

$html = <<<HTML
<html>
<head>
  <meta charset="UTF-8">
  <title>PHP Tidy完全ガイド</title>
</head>
<body><p>本文</p></body>
</html>
HTML;

$result = $checker->check($html);
$checker->report($result);

実行結果:

[✓] title           PHP Tidy完全ガイド
[✗] description     (未設定)
[✓] charset         UTF-8

例5:OOPスタイル(head())での記述

<?php
class OopHeadNodeDemo
{
    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 getHeadInfo(): array
    {
        // OOPスタイルの head() は tidy_get_head() と同等
        $head = $this->tidy->head();

        $childNames = [];
        if ($head->hasChildren()) {
            foreach ($head->child as $child) {
                if ($child->name) {
                    $childNames[] = $child->name;
                }
            }
        }

        return [
            'name'        => $head->name,
            'child_count' => count($childNames),
            'child_tags'  => $childNames,
        ];
    }
}

$demo = new OopHeadNodeDemo();
$info = $demo->parse(<<<HTML
<html>
<head>
  <meta charset="UTF-8">
  <title>テスト</title>
  <link rel="stylesheet" href="style.css">
  <script src="app.js"></script>
</head>
<body></body>
</html>
HTML)->getHeadInfo();

print_r($info);

実行結果:

Array
(
    [name] => head
    [child_count] => 4
    [child_tags] => Array
        (
            [0] => meta
            [1] => title
            [2] => link
            [3] => script
        )

)

例6:head の中身を修正・注入して返す

<?php
class HeadInjector
{
    public function injectMeta(string $html, array $newMetas): string
    {
        // Tidy でまず整形
        $tidy = tidy_parse_string($html, [
            'output-xhtml' => true,
            'wrap'         => 0,
        ], 'UTF8');
        tidy_clean_repair($tidy);

        $repairedHtml = (string) $tidy;

        // 新しい<meta>タグを </head> の直前に挿入
        $insertHtml = '';
        foreach ($newMetas as $attrs) {
            $attrStr = '';
            foreach ($attrs as $key => $val) {
                $attrStr .= ' ' . $key . '="' . htmlspecialchars($val) . '"';
            }
            $insertHtml .= "<meta{$attrStr}>\n";
        }

        return str_replace('</head>', $insertHtml . '</head>', $repairedHtml);
    }
}

$injector = new HeadInjector();

$html = '<html><head><title>テスト</title></head><body><p>本文</p></body></html>';

$result = $injector->injectMeta($html, [
    ['name' => 'robots',      'content' => 'index,follow'],
    ['name' => 'description', 'content' => 'PHPの解説ページです。'],
]);

echo $result;

実行結果:

<!DOCTYPE html>
<html>
<head>
<title>テスト</title>
<meta name="robots" content="index,follow">
<meta name="description" content="PHPの解説ページです。">
</head>
<body>
<p>本文</p>
</body>
</html>

例7:<head> が存在しないHTMLへの安全なアクセス

<?php
class SafeHeadNodeAccessor
{
    public function getChildTagNames(string $html): array
    {
        $tidy = tidy_parse_string($html, ['output-xhtml' => true], 'UTF8');
        tidy_clean_repair($tidy);

        $head = tidy_get_head($tidy);

        // Tidyは<head>を自動補完するので通常は必ず存在するが、念のため確認
        if (!isset($head) || !$head->hasChildren()) {
            return [];
        }

        $names = [];
        foreach ($head->child as $child) {
            if ($child->name !== null) {
                $names[] = $child->name;
            }
        }

        return array_unique($names);
    }
}

$accessor = new SafeHeadNodeAccessor();

// <head> タグなしのフラグメント → Tidy が自動補完する
$tags = $accessor->getChildTagNames('<p>headタグなし</p>');
echo "補完後の head の子タグ: " . implode(', ', $tags) . PHP_EOL;

// 通常のHTML
$tags2 = $accessor->getChildTagNames(
    '<html><head><meta charset="UTF-8"><title>タイトル</title></head><body></body></html>'
);
echo "通常のHTMLの head の子タグ: " . implode(', ', $tags2) . PHP_EOL;

実行結果:

補完後の head の子タグ: (空)
通常のHTMLの head の子タグ: meta, title

関連関数との比較

関数OOPスタイル返却する要素
tidy_get_head()head()<head> 要素の tidyNode
tidy_get_body()body()<body> 要素の tidyNode
tidy_get_html()html()<html> 要素の tidyNode
tidy_get_root()root()ドキュメントルートの tidyNode
tidy_get_output()value プロパティドキュメント全体のHTML文字列

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

  1. $node->value には <head> タグ自体が含まれる tidy_get_body() と同様に、$head->value<head>...</head> のタグ全体を含みます。<head> タグを除いた中身だけが欲しい場合は、正規表現で外周タグを取り除く処理が必要です。
  2. <head> なしのHTMLでもTidyが自動補完する <head> タグが存在しないHTMLフラグメントを渡しても、Tidyが自動的に <head> を補完します。ただし補完された <head> の子ノードは空になるため、hasChildren() で確認してからアクセスしましょう。
  3. $node->childnull の場合がある <head> 要素が空(子要素なし)の場合、$node->childnull になります。hasChildren() を使って存在を確認してからアクセスするのが安全です。
  4. tidy_clean_repair() を呼ばないとノードが不完全 parse だけでは修正が適用されておらず、<head> ノードが不完全な状態で返る場合があります。必ず cleanRepair() 後に tidy_get_head() を呼び出してください。
  5. tidyNodeDOMNode とは別物 Tidy の tidyNode は PHP 組み込みの DOMDocument で扱う DOMNode とは別のオブジェクトです。DOMDocument のメソッドは使えません。DOMDocument で高度な操作が必要な場合は、$head->value の文字列を DOMDocument::loadHTML() に渡して変換してください。

まとめ

ポイント内容
役割HTMLの <head> 要素を tidyNode オブジェクトとして取得する
引数tidyオブジェクト
戻り値tidyNode(head要素のノード)
OOPスタイル$tidy->head()
$node->value<head> タグを含む head 全体のHTML文字列
よく組み合わせる関数tidy_clean_repair(), tidy_get_body(), $node->child, $node->hasChildren()
主な用途title抽出、metaタグ収集、linkタグ解析、SEOチェック、headへのタグ注入
注意点value<head> タグが含まれる、child は null チェックが必要、DOMNodeとは別物

tidy_get_head()tidy_get_body()<head> 版として、メタ情報の読み取り・SEOチェック・タグの収集といった用途でシンプルに活用できます。tidy_get_body() と組み合わせることで、HTMLのページ全体を <head><body> に分けて別々に処理するワークフローを組み立てることができます。

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