[PHP]tidy_get_output完全解説|Tidyで整形・修復したHTMLを文字列として取得する方法

PHP

1. 関数概要

tidy_get_output は、PHP の Tidy 拡張によって解析・修復・整形された HTML/XHTML ドキュメントを文字列として取得する関数です。tidy_clean_repair() でクリーニングを行った後にこの関数を呼び出すことで、修正済みの HTML を変数に格納し、出力・保存・加工に利用できます。

項目内容
関数名tidy_get_output
所属拡張Tidy
戻り値の型string
手続き型 / OOP手続き型(OOP 版: (string) $tidy キャストまたは $tidy->value プロパティ)
PHP バージョンPHP 5 以降
公式ドキュメントhttps://www.php.net/manual/ja/tidy.getoutput.php

2. 構文

// 手続き型
tidy_get_output(tidy $tidy): string

// オブジェクト指向型(プロパティアクセス)
$html = $tidy->value;

// オブジェクト指向型(文字列キャスト)
$html = (string) $tidy;

パラメータ

パラメータ説明
$tidytidytidy_parse_string() 等で生成し、tidy_clean_repair() を適用済みの tidy オブジェクト

戻り値

クリーニング・整形済みの HTML/XHTML 文字列。

注意: tidy_clean_repair() を呼び出す前に tidy_get_output() を実行すると、修復前の元のソースが返されます。必ずクリーニング後に呼び出してください。


3. 動作概念図

┌──────────────────────────────────────────────────────────────┐
│  入力: 不正・未整形の HTML 文字列                             │
│  "<html><body><p>未閉じタグ<br>改行なし</body>"              │
└───────────────────────┬──────────────────────────────────────┘
                        │ tidy_parse_string()
                        ▼
┌──────────────────────────────────────────────────────────────┐
│  Tidy 解析エンジン(tidy オブジェクト生成)                   │
│  ・DOCTYPE 検出                                               │
│  ・構文エラー収集                                             │
│  ・内部ツリー構築                                             │
└───────────────────────┬──────────────────────────────────────┘
                        │ tidy_clean_repair()
                        ▼
┌──────────────────────────────────────────────────────────────┐
│  修復・整形処理                                               │
│  ・タグの自動補完      ・インデント整形                       │
│  ・属性の正規化        ・文字エンコード変換                   │
│  ・DOCTYPE 付与        ・XHTML 変換(オプション)             │
└───────────────────────┬──────────────────────────────────────┘
                        │ tidy_get_output()
                        ▼
┌──────────────────────────────────────────────────────────────┐
│  出力: 整形済み HTML/XHTML 文字列(string)                   │
│  "<!DOCTYPE html>\n<html>\n  <head>...</head>\n  <body>..."  │
└──────────────────────────────────────────────────────────────┘

4. 基本的な使い方

<?php
// 意図的に不正な HTML を用意
$html = '<html><body><p>閉じタグなし<br>テキスト</body>';

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

$output = tidy_get_output($tidy);
echo $output;

出力例:

<!DOCTYPE html>
<html>
<head>
  <title></title>
</head>
<body>
  <p>閉じタグなし<br>
  テキスト</p>
</body>
</html>

5. 実践的なコード例

例1: HTML を整形して文字列で受け取る基本クラス

<?php
class HtmlFormatter
{
    private tidy $tidy;

    public function __construct(
        private readonly array $config = ['indent' => true, 'wrap' => 80]
    ) {}

    public function format(string $html): string
    {
        $this->tidy = tidy_parse_string($html, $this->config, 'UTF8');
        tidy_clean_repair($this->tidy);
        return tidy_get_output($this->tidy);
    }
}

$formatter = new HtmlFormatter();
$dirty = '<html><body><P>段落<br>テキスト<div>ブロック</div></P></body></html>';
echo $formatter->format($dirty);

出力例:

<!DOCTYPE html>
<html>
<head>
  <title></title>
</head>
<body>
  <p>段落<br>
  テキスト</p>
  <div>ブロック</div>
</body>
</html>

例2: OOP スタイルで $tidy->value を使う

<?php
class TidyOopOutputFetcher
{
    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 getOutput(): string
    {
        // OOP でのプロパティアクセス
        return $this->tidy->value;
    }

    public function getCasted(): string
    {
        // 文字列キャストでも同じ結果
        return (string) $this->tidy;
    }
}

$html = '<html><body><p>OOPサンプル</p></body></html>';
$fetcher = new TidyOopOutputFetcher($html, ['indent' => true]);

echo "--- value プロパティ ---" . PHP_EOL;
echo $fetcher->getOutput();

echo PHP_EOL . "--- string キャスト ---" . PHP_EOL;
echo $fetcher->getCasted();

出力例:

--- value プロパティ ---
<!DOCTYPE html>
<html>
<head>
  <title></title>
</head>
<body>
  <p>OOPサンプル</p>
</body>
</html>

--- string キャスト ---
(同一の出力)

例3: 整形済み HTML をファイルに保存するクラス

<?php
class HtmlFileSaver
{
    public function __construct(
        private readonly string $outputDir
    ) {
        if (!is_dir($this->outputDir)) {
            mkdir($this->outputDir, 0755, true);
        }
    }

    public function save(string $html, string $filename): string
    {
        $config = ['indent' => true, 'wrap' => 120, 'doctype' => 'html5'];
        $tidy   = tidy_parse_string($html, $config, 'UTF8');
        tidy_clean_repair($tidy);

        $output   = tidy_get_output($tidy);
        $filepath = $this->outputDir . DIRECTORY_SEPARATOR . $filename;
        file_put_contents($filepath, $output);

        echo "保存完了: {$filepath} (" . strlen($output) . " bytes)" . PHP_EOL;
        return $filepath;
    }
}

$saver = new HtmlFileSaver('/tmp/tidy_output');
$dirty = '<HTML><BODY><h1>タイトル<p>本文テキスト</BODY>';
$saver->save($dirty, 'cleaned.html');

出力例:

保存完了: /tmp/tidy_output/cleaned.html (152 bytes)

例4: 修復前後を比較して差分を表示するクラス

<?php
class HtmlDiffViewer
{
    public function compare(string $original): void
    {
        $tidy = tidy_parse_string($original, ['indent' => true], 'UTF8');
        tidy_clean_repair($tidy);
        $repaired = tidy_get_output($tidy);

        $originalLines = explode("\n", $original);
        $repairedLines = explode("\n", $repaired);

        echo "=== 修復前: " . count($originalLines) . " 行 / "
            . strlen($original) . " bytes ===" . PHP_EOL;
        echo $original . PHP_EOL . PHP_EOL;

        echo "=== 修復後: " . count($repairedLines) . " 行 / "
            . strlen($repaired) . " bytes ===" . PHP_EOL;
        echo $repaired . PHP_EOL;
    }
}

$viewer = new HtmlDiffViewer();
$viewer->compare('<html><body><p>未閉じ<b>太字</body>');

出力例:

=== 修復前: 1 行 / 38 bytes ===
<html><body><p>未閉じ<b>太字</body>

=== 修復後: 14 行 / 210 bytes ===
<!DOCTYPE html>
<html>
<head>
  <title></title>
</head>
<body>
  <p>未閉じ<b>太字</b></p>
</body>
</html>

例5: XHTML 形式で出力するクラス

<?php
class XhtmlConverter
{
    private array $config = [
        'output-xhtml'   => true,
        'indent'         => true,
        'wrap'           => 80,
        'char-encoding'  => 'utf8',
    ];

    public function convert(string $html): string
    {
        $tidy = tidy_parse_string($html, $this->config, 'UTF8');
        tidy_clean_repair($tidy);
        return tidy_get_output($tidy);
    }

    public function isXhtml(string $output): bool
    {
        return str_contains($output, 'xmlns="http://www.w3.org/1999/xhtml"');
    }
}

$converter = new XhtmlConverter();
$html = '<html><body><br><img src="test.jpg"></body></html>';

$xhtml = $converter->convert($html);
echo $xhtml . PHP_EOL;
echo "XHTML 変換確認: " . ($converter->isXhtml($xhtml) ? '✅ OK' : '❌ NG') . PHP_EOL;

出力例:

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
  <title></title>
</head>
<body>
  <br />
  <img src="test.jpg" alt="" />
</body>
</html>

XHTML 変換確認: ✅ OK

例6: エラー情報と整形済み出力をセットで返すクラス

<?php
class TidyAnalysisResult
{
    public function __construct(
        public readonly string $output,
        public readonly int    $status,
        public readonly string $errorBuffer,
        public readonly int    $htmlVersion,
    ) {}
}

class TidyFullAnalyzer
{
    public function analyze(string $html, array $config = []): TidyAnalysisResult
    {
        $tidy = tidy_parse_string($html, $config + ['indent' => true], 'UTF8');
        tidy_clean_repair($tidy);

        return new TidyAnalysisResult(
            output:      tidy_get_output($tidy),
            status:      tidy_get_status($tidy),
            errorBuffer: tidy_get_error_buffer($tidy) ?? '',
            htmlVersion: tidy_get_html_ver($tidy),
        );
    }
}

$analyzer = new TidyFullAnalyzer();
$result   = $analyzer->analyze('<html><body><p>テスト<br></body></html>');

echo "=== 整形済み出力 ===" . PHP_EOL;
echo $result->output . PHP_EOL;

echo "=== 解析結果 ===" . PHP_EOL;
echo "ステータス    : " . $result->status      . PHP_EOL;
echo "HTMLバージョン: " . $result->htmlVersion  . PHP_EOL;
echo "エラーバッファ: " . ($result->errorBuffer ?: 'なし') . PHP_EOL;

出力例:

=== 整形済み出力 ===
<!DOCTYPE html>
<html>
<head>
  <title></title>
</head>
<body>
  <p>テスト<br></p>
</body>
</html>

=== 解析結果 ===
ステータス    : 0
HTMLバージョン: 0
エラーバッファ: なし

例7: 複数ページを一括整形して配列で返すクラス

<?php
class BulkHtmlCleaner
{
    private array $config;

    public function __construct(array $config = ['indent' => true, 'wrap' => 80])
    {
        $this->config = $config;
    }

    /**
     * @param array<string, string> $pages  キー: ページ名, 値: HTML文字列
     * @return array<string, string>        キー: ページ名, 値: 整形済みHTML
     */
    public function cleanAll(array $pages): array
    {
        $results = [];
        foreach ($pages as $name => $html) {
            $tidy = tidy_parse_string($html, $this->config, 'UTF8');
            tidy_clean_repair($tidy);
            $results[$name] = tidy_get_output($tidy);
        }
        return $results;
    }

    public function printSummary(array $results): void
    {
        echo "=== 一括整形サマリー ===" . PHP_EOL;
        foreach ($results as $name => $output) {
            printf("%-15s => %d bytes%s", $name, strlen($output), PHP_EOL);
        }
    }
}

$cleaner = new BulkHtmlCleaner();
$pages   = [
    'index'   => '<html><body><h1>トップ</h1><p>概要</body>',
    'about'   => '<html><body><h2>会社概要<p>内容</h2></body></html>',
    'contact' => '<HTML><BODY><FORM><INPUT type=text></FORM></BODY></HTML>',
];

$results = $cleaner->cleanAll($pages);
$cleaner->printSummary($results);

echo PHP_EOL . "=== index ページの整形結果 ===" . PHP_EOL;
echo $results['index'];

出力例:

=== 一括整形サマリー ===
index           => 183 bytes
about           => 187 bytes
contact         => 219 bytes

=== index ページの整形結果 ===
<!DOCTYPE html>
<html>
<head>
  <title></title>
</head>
<body>
  <h1>トップ</h1>
  <p>概要</p>
</body>
</html>

6. 関連関数との比較

関数名用途戻り値
tidy_get_output()整形済み HTML を文字列で取得string
tidy_clean_repair()HTML の修復・整形を実行(出力は行わない)bool
tidy_parse_string()HTML 文字列を解析して tidy オブジェクト生成tidy|false
tidy_parse_file()HTML ファイルを解析して tidy オブジェクト生成tidy|false
tidy_get_error_buffer()解析・修復時のエラーメッセージを取得string|false
tidy_get_status()解析結果のステータス(0=正常, 1=警告, 2=エラー)を取得int
tidy_get_html_ver()解析済み HTML のバージョン番号を取得int

7. よくある落とし穴と注意点

① tidy_clean_repair() を呼ぶ前に実行すると未修復のソースが返る

最も重要な注意点です。tidy_get_output() は内部バッファをそのまま返すため、tidy_clean_repair() を忘れると修復前の HTML が返ります。

// NG: cleanRepair なしで出力
$tidy = tidy_parse_string($html, [], 'UTF8');
echo tidy_get_output($tidy); // 修復されていない可能性がある

// OK: cleanRepair 後に出力
$tidy = tidy_parse_string($html, [], 'UTF8');
tidy_clean_repair($tidy);
echo tidy_get_output($tidy); // 修復済み

② OOP では $tidy->value または (string) $tidy で同等の結果が得られる

手続き型の tidy_get_output($tidy) と、OOP の $tidy->value および (string) $tidy はすべて同じ出力を返します。プロジェクトのスタイルに合わせて統一してください。

// 以下の3つはすべて同じ結果
$out1 = tidy_get_output($tidy);
$out2 = $tidy->value;
$out3 = (string) $tidy;

③ 設定オプションによって出力内容が大きく変わる

indentwrapoutput-xhtmldoctype などの設定によって出力形式が異なります。意図しない DOCTYPE や XML 宣言が付与されることがあるため、tidy_get_config() で現在の設定を確認しながら調整してください。

④ 文字エンコードの指定を忘れない

tidy_parse_string() の第3引数でエンコードを明示的に指定しないと、日本語が文字化けする場合があります。

// NG: エンコード未指定(文字化けの恐れ)
$tidy = tidy_parse_string($html, []);

// OK: UTF-8 を明示指定
$tidy = tidy_parse_string($html, [], 'UTF8');

8. まとめ

項目内容
主な用途Tidy で修復・整形した HTML を文字列として取得する
戻り値string(整形済み HTML/XHTML)
必須の前提tidy_clean_repair() を先に呼び出すこと
OOP 版$tidy->value または (string) $tidy
よく使う組み合わせtidy_parse_string()tidy_clean_repair()tidy_get_output()
注意点エンコード指定・clean_repair 忘れ・設定による出力変化

tidy_get_output() は Tidy を使った HTML 処理の最終ステップとして必ず登場する関数です。tidy_parse_string()tidy_clean_repair()tidy_get_output() の3ステップをひとつのパターンとして覚えておくと、HTML の自動修復・整形をスムーズに実装できます。

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