[PHP]tidy_repair_string完全解説|HTML文字列をワンステップで修復・整形して返す最速の方法

PHP

1. 関数概要

tidy_repair_string は、PHP の Tidy 拡張が提供する HTML/XHTML 文字列の解析・修復・整形をワンステップで行い、結果を文字列で返す関数です。tidy_parse_string()tidy_clean_repair()tidy_get_output() の3ステップを1回の呼び出しにまとめたショートカット関数であり、tidy オブジェクトを生成せずに整形済み文字列を即座に得られます。

項目内容
関数名tidy_repair_string
所属拡張Tidy
戻り値の型string|false
手続き型 / OOP手続き型のみ(OOP 版なし)
PHP バージョンPHP 5 以降
公式ドキュメントhttps://www.php.net/manual/ja/tidy.repairstring.php

2. 構文

tidy_repair_string(
    string $string,
    array|string|null $config = null,
    ?string $encoding = null
): string|false

パラメータ

パラメータ説明
$stringstring修復する HTML/XHTML/XML 文字列
$configarray|string|nullTidy オプションの連想配列、または tidyrc 設定ファイルのパス。null でデフォルト設定
$encodingstring|null入出力エンコーディング(例: 'UTF8', 'latin1')。null でデフォルト(ascii

戻り値

結果戻り値
成功修復・整形済みの HTML 文字列(string
失敗false

3. tidy_repair_string と関連関数の比較

観点tidy_repair_string()tidy_repair_file()tidy_parse_string()
入力HTML 文字列ファイルパス / URLHTML 文字列
戻り値string|falsestring|falsetidy|false
tidy オブジェクト不要・生成されない不要・生成されない生成される
ステータス取得❌ 不可❌ 不可✅ 可
エラーバッファ取得❌ 不可❌ 不可✅ 可
DOM ツリー走査❌ 不可❌ 不可✅ 可
向いている用途文字列の簡易整形ファイルの簡易整形詳細な検査・分析

4. 動作概念図

┌──────────────────────────────────────────────────────────┐
│  入力: HTML 文字列(メモリ上)                            │
│  "<html><body><P>未閉じ<br></body>"                       │
└─────────────────────┬────────────────────────────────────┘
                      │
                      ▼
┌──────────────────────────────────────────────────────────┐
│  tidy_repair_string($string, $config, $encoding)          │
│                                                           │
│  内部で自動実行:                                          │
│  ① tidy_parse_string()  文字列の解析                     │
│  ② tidy_clean_repair()  修復・整形                       │
│  ③ tidy_get_output()    文字列化                         │
└─────────────────────┬────────────────────────────────────┘
                      │
          ┌───────────┴───────────┐
      成功 │                       │ 失敗
          ▼                       ▼
   整形済み HTML 文字列         false
   (string)

5. 基本的な使い方

<?php
$dirty = '<HTML><BODY><P>未閉じタグ<br>テキスト<b>太字</BODY>';

$clean = tidy_repair_string($dirty, ['indent' => true, 'wrap' => 80], 'UTF8');

if ($clean !== false) {
    echo $clean;
} else {
    echo "修復に失敗しました。" . PHP_EOL;
}

出力例:

<!DOCTYPE html>
<html>
<head>
  <title></title>
</head>
<body>
  <p>未閉じタグ<br>
  テキスト<b>太字</b></p>
</body>
</html>

6. 実践的なコード例

例1: HTML を修復して返すシンプルなクラス

<?php
class HtmlRepairService
{
    public function __construct(
        private readonly array  $config   = ['indent' => true, 'wrap' => 80],
        private readonly string $encoding = 'UTF8'
    ) {}

    public function repair(string $html): string
    {
        $result = tidy_repair_string($html, $this->config, $this->encoding);
        return $result !== false ? $result : $html; // 失敗時はそのまま返す
    }

    public function repairOrFail(string $html): string
    {
        $result = tidy_repair_string($html, $this->config, $this->encoding);
        if ($result === false) {
            throw new \RuntimeException('tidy_repair_string() による修復に失敗しました。');
        }
        return $result;
    }
}

$service = new HtmlRepairService();
$dirty   = '<HTML><BODY><H1>タイトル<P>本文テキスト</BODY>';

echo $service->repair($dirty);

出力例:

<!DOCTYPE html>
<html>
<head>
  <title></title>
</head>
<body>
  <h1>タイトル</h1>
  <p>本文テキスト</p>
</body>
</html>

例2: 複数の HTML を一括修復するクラス

<?php
class BulkHtmlRepairer
{
    private int $successCount = 0;
    private int $failCount    = 0;

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

    /**
     * @param array<string, string> $pages
     * @return array<string, string|false>
     */
    public function repairAll(array $pages): array
    {
        $results = [];
        foreach ($pages as $name => $html) {
            $result = tidy_repair_string($html, $this->config, 'UTF8');
            $results[$name] = $result;
            $result !== false ? $this->successCount++ : $this->failCount++;
        }
        return $results;
    }

    public function printSummary(array $results): void
    {
        echo "=== 一括修復サマリー ===" . PHP_EOL;
        foreach ($results as $name => $output) {
            $icon  = $output !== false ? '✅' : '❌';
            $bytes = $output !== false ? strlen($output) . ' bytes' : '失敗';
            printf("  %s %-15s → %s%s", $icon, $name, $bytes, PHP_EOL);
        }
        echo PHP_EOL . "成功: {$this->successCount} / 失敗: {$this->failCount}" . PHP_EOL;
    }
}

$repairer = new BulkHtmlRepairer();
$pages    = [
    'index'   => '<html><body><h1>トップ<p>本文</body>',
    'about'   => '<HTML><BODY><H2>概要</H2><P>テキスト</BODY>',
    'contact' => '<html><body><form><input type=text name=email></form></body></html>',
    'news'    => '<!DOCTYPE html><html><head><title>News</title></head><body><p>最新情報</p></body></html>',
];

$results = $repairer->repairAll($pages);
$repairer->printSummary($results);

echo PHP_EOL . "=== index の修復結果 ===" . PHP_EOL;
echo $results['index'];

出力例:

=== 一括修復サマリー ===
  ✅ index           → 195 bytes
  ✅ about           → 198 bytes
  ✅ contact         → 226 bytes
  ✅ news            → 183 bytes

成功: 4 / 失敗: 0

=== index の修復結果 ===
<!DOCTYPE html>
<html>
<head>
  <title></title>
</head>
<body>
  <h1>トップ</h1>
  <p>本文</p>
</body>
</html>

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

<?php
class HtmlRepairDiffViewer
{
    public function compare(string $original, array $config = ['indent' => true]): void
    {
        $repaired = tidy_repair_string($original, $config, 'UTF8');

        if ($repaired === false) {
            echo "❌ 修復に失敗しました。" . PHP_EOL;
            return;
        }

        $origLines     = substr_count($original, "\n") + 1;
        $repairedLines = substr_count($repaired, "\n") + 1;

        echo "=== 修復前 ===" . PHP_EOL;
        printf("  %d bytes / %d 行%s", strlen($original), $origLines, PHP_EOL);
        echo $original . PHP_EOL . PHP_EOL;

        echo "=== 修復後 ===" . PHP_EOL;
        printf("  %d bytes / %d 行 (差分: %+d bytes)%s",
            strlen($repaired),
            $repairedLines,
            strlen($repaired) - strlen($original),
            PHP_EOL
        );
        echo $repaired;
    }
}

$viewer = new HtmlRepairDiffViewer();
$viewer->compare('<html><body><p>未閉じ<b>太字<br>テキスト</body>');

出力例:

=== 修復前 ===
  46 bytes / 1 行
<html><body><p>未閉じ<b>太字<br>テキスト</body>

=== 修復後 ===
  220 bytes / 14 行 (差分: +174 bytes)
<!DOCTYPE html>
<html>
<head>
  <title></title>
</head>
<body>
  <p>未閉じ<b>太字<br>
  テキスト</b></p>
</body>
</html>

例4: XHTML 形式で修復・出力するクラス

<?php
class XhtmlRepairConverter
{
    private readonly array $config;

    public function __construct(int $wrapWidth = 80)
    {
        $this->config = [
            'output-xhtml'  => true,
            'indent'        => true,
            'wrap'          => $wrapWidth,
            'char-encoding' => 'utf8',
        ];
    }

    public function convert(string $html): string|false
    {
        return tidy_repair_string($html, $this->config, 'UTF8');
    }

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

$converter = new XhtmlRepairConverter();
$html      = '<html><body><br><img src="photo.jpg"><p>テキスト</body></html>';
$xhtml     = $converter->convert($html);

if ($xhtml !== false) {
    echo "XHTML 変換確認: " . ($converter->isXhtmlOutput($xhtml) ? '✅ OK' : '❌ NG') . PHP_EOL;
    echo $xhtml;
}

出力例:

XHTML 変換確認: ✅ OK
<?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="photo.jpg" alt="" />
  <p>テキスト</p>
</body>
</html>

例5: ユーザー入力を安全に整形するサニタイザークラス

<?php
class UserInputHtmlSanitizer
{
    private readonly array $config;

    public function __construct()
    {
        $this->config = [
            'output-xhtml'        => false,
            'indent'              => true,
            'wrap'                => 0,
            'drop-empty-elements' => true,
            'clean'               => true,
            'show-warnings'       => false,
            'quiet'               => true,
        ];
    }

    public function sanitize(string $userInput): string
    {
        // インラインコンテンツとして div でラップ
        $wrapped  = '<div>' . $userInput . '</div>';
        $repaired = tidy_repair_string($wrapped, $this->config, 'UTF8');

        if ($repaired === false) {
            return htmlspecialchars($userInput, ENT_QUOTES, 'UTF-8');
        }

        // body 内のみ抽出
        if (preg_match('/<body[^>]*>(.*?)<\/body>/si', $repaired, $m)) {
            return trim($m[1]);
        }

        return $repaired;
    }
}

$sanitizer = new UserInputHtmlSanitizer();

$inputs = [
    '<p onclick="alert(1)">テキスト</p><script>evil()</script><b>太字</b>',
    '<h2>見出し</h2><ul><li>項目1</li><li>項目2</ul>',
    'プレーンテキスト(タグなし)',
];

foreach ($inputs as $i => $input) {
    echo "=== 入力 " . ($i + 1) . " ===" . PHP_EOL;
    echo $sanitizer->sanitize($input) . PHP_EOL . PHP_EOL;
}

出力例:

=== 入力 1 ===
<div>
  <p>テキスト</p>
  <b>太字</b>
</div>

=== 入力 2 ===
<div>
  <h2>見出し</h2>
  <ul>
    <li>項目1</li>
    <li>項目2</li>
  </ul>
</div>

=== 入力 3 ===
<div>
  プレーンテキスト(タグなし)
</div>

例6: tidyrc 設定ファイルを使って修復するクラス

<?php
class TidyRcStringRepairer
{
    private string $rcPath;

    public function __construct(array $options)
    {
        $this->rcPath = tempnam(sys_get_temp_dir(), 'tidyrc_');
        $lines = [];
        foreach ($options as $key => $value) {
            $val    = is_bool($value) ? ($value ? 'yes' : 'no') : (string) $value;
            $lines[] = "{$key}: {$val}";
        }
        file_put_contents($this->rcPath, implode("\n", $lines));
    }

    public function repair(string $html, string $encoding = 'UTF8'): string|false
    {
        // $config に設定ファイルパス(string)を渡す
        return tidy_repair_string($html, $this->rcPath, $encoding);
    }

    public function __destruct()
    {
        if (file_exists($this->rcPath)) {
            unlink($this->rcPath);
        }
    }
}

$repairer = new TidyRcStringRepairer([
    'indent'       => true,
    'wrap'         => 100,
    'output-xhtml' => false,
    'quiet'        => true,
]);

$html   = '<html><body><p>tidyrc 経由のテスト</p></body></html>';
$result = $repairer->repair($html);

echo $result !== false ? $result : "❌ 修復失敗" . PHP_EOL;

出力例:

<!DOCTYPE html>
<html>
<head>
  <title></title>
</head>
<body>
  <p>tidyrc 経由のテスト</p>
</body>
</html>

例7: 修復結果の統計を収集するレポータークラス

<?php
class HtmlRepairStatReporter
{
    /** @var array<string, array{original: int, repaired: int|null, success: bool}> */
    private array $stats = [];

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

    public function add(string $label, string $html): self
    {
        $repaired = tidy_repair_string($html, $this->config, 'UTF8');
        $this->stats[$label] = [
            'original' => strlen($html),
            'repaired' => $repaired !== false ? strlen($repaired) : null,
            'success'  => $repaired !== false,
        ];
        return $this;
    }

    public function printReport(): void
    {
        echo "=== 修復統計レポート ===" . PHP_EOL;
        printf("  %-18s %8s %8s %8s%s", "ラベル", "修復前", "修復後", "差分", PHP_EOL);
        echo "  " . str_repeat('-', 46) . PHP_EOL;

        $totalOrig     = 0;
        $totalRepaired = 0;
        $successCount  = 0;

        foreach ($this->stats as $label => $s) {
            $icon      = $s['success'] ? '✅' : '❌';
            $repStr    = $s['repaired'] !== null ? $s['repaired'] . 'B' : '失敗';
            $diffStr   = $s['repaired'] !== null
                ? sprintf('%+d', $s['repaired'] - $s['original']) . 'B'
                : '-';

            printf("  %s %-15s %8s %8s %8s%s",
                $icon, $label,
                $s['original'] . 'B',
                $repStr,
                $diffStr,
                PHP_EOL
            );

            $totalOrig += $s['original'];
            if ($s['repaired'] !== null) {
                $totalRepaired += $s['repaired'];
                $successCount++;
            }
        }

        echo "  " . str_repeat('-', 46) . PHP_EOL;
        printf("  %-18s %8s %8s %8s%s",
            "合計 ({$successCount}件成功)",
            $totalOrig     . 'B',
            $totalRepaired . 'B',
            sprintf('%+d', $totalRepaired - $totalOrig) . 'B',
            PHP_EOL
        );
    }

    public function toJson(): string
    {
        return json_encode(
            ['stats' => $this->stats, 'generated_at' => date('c')],
            JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE
        );
    }
}

$reporter = new HtmlRepairStatReporter();
$reporter
    ->add('正常な HTML',   '<!DOCTYPE html><html><head><title>T</title></head><body><p>OK</p></body></html>')
    ->add('大文字タグ',   '<HTML><BODY><H1>見出し</H1><P>本文</P></BODY></HTML>')
    ->add('未閉じタグ',   '<html><body><p>段落<b>太字<br>テキスト</body>')
    ->add('属性なしタグ', '<html><body><img src="a.jpg"><input type=text></body></html>');

$reporter->printReport();

出力例:

=== 修復統計レポート ===
  ラベル                修復前    修復後      差分
  ----------------------------------------------
  ✅ 正常な HTML         87B     183B      +96B
  ✅ 大文字タグ          54B     195B     +141B
  ✅ 未閉じタグ          46B     220B     +174B
  ✅ 属性なしタグ        65B     234B     +169B
  ----------------------------------------------
  合計 (4件成功)        252B     832B     +580B

7. 関連関数との比較

関数名入力戻り値tidy オブジェクト
tidy_repair_string()HTML 文字列string|false不要・生成されない
tidy_repair_file()ファイルパス / URLstring|false不要・生成されない
tidy_parse_string()HTML 文字列tidy|false生成される
tidy_parse_file()ファイルパス / URLtidy|false生成される
tidy_clean_repair()tidy オブジェクトbool必要
tidy_get_output()tidy オブジェクトstring必要

使い分けのポイント: 「修復済みの文字列だけ欲しい」なら tidy_repair_string()、「ステータス・エラーバッファ・DOM ツリーも検査したい」なら tidy_parse_string() + 3ステップ処理を選んでください。


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

① ステータスやエラーバッファは取得できない

tidy_repair_string() は tidy オブジェクトを返さないため、tidy_get_status()tidy_get_error_buffer() を呼び出せません。警告・エラーの詳細が必要な場合は tidy_parse_string() を使ってください。

// NG: tidy オブジェクトが存在しない
$result = tidy_repair_string($html, [], 'UTF8');
tidy_get_status($result); // $result は string|false なので使えない

// OK: 詳細が必要なら tidy_parse_string() を使う
$tidy = tidy_parse_string($html, [], 'UTF8');
tidy_clean_repair($tidy);
$status = tidy_get_status($tidy);
$output = tidy_get_output($tidy);

② OOP 版は存在しない

tidy_repair_string() には OOP 版($tidy->repairString() 相当)がありません。OOP スタイルで同等の処理を行う場合は $tidy->parseString() + $tidy->cleanRepair() + $tidy->value を使ってください。

// OOP スタイルで同等の処理
$tidy = new tidy();
$tidy->parseString($html, ['indent' => true], 'UTF8');
$tidy->cleanRepair();
$repaired = $tidy->value;

③ エンコーディング名は Tidy 固有の表記を使う

'UTF-8' ではなく 'UTF8''ISO-8859-1' ではなく 'latin1' のように Tidy 固有の表記が必要です。日本語を扱う場合は必ず 'UTF8' を指定してください。

tidy_repair_string($html, [], 'UTF8');   // ✅ 正しい
tidy_repair_string($html, [], 'UTF-8');  // ⚠️  認識されない場合がある

④ false 返却時のフォールバックを用意する

tidy_repair_string()false を返した場合に何もしないと、後続の処理で予期しないエラーが発生します。フォールバックを明示的に実装してください。

// NG: false を文字列として使う恐れ
$output = tidy_repair_string($html, [], 'UTF8');
echo $output; // false の場合に何も表示されないか警告

// OK: 必ず確認する
$output = tidy_repair_string($html, [], 'UTF8');
if ($output === false) {
    $output = htmlspecialchars($html, ENT_QUOTES, 'UTF-8'); // フォールバック
}
echo $output;

⑤ $config は配列か設定ファイルパスのどちらかを渡す

オプションは ['indent' => true] のような連想配列か、tidyrc ファイルのパス文字列を渡します。両方を同時には渡せません。

// 配列でオプション指定
tidy_repair_string($html, ['indent' => true, 'wrap' => 80], 'UTF8');

// tidyrc ファイルのパスで指定
tidy_repair_string($html, '/path/to/.tidyrc', 'UTF8');

9. まとめ

項目内容
主な用途HTML 文字列をワンステップで修復・整形して文字列を得る
戻り値string(修復済み HTML)/ false(失敗)
OOP 版なし
tidy オブジェクト生成されない(ステータス・エラー取得不可)
エンコーディング'UTF8', 'latin1' など Tidy 固有の表記を使う
詳細な検査が必要なときtidy_parse_string() の3ステップ処理に切り替える
tidy_repair_file() との違い入力がファイルパスではなく文字列

tidy_repair_string() は Tidy ファミリーの中で最もシンプルに使える関数です。tidy オブジェクトを介さず1行で整形済み文字列を得られるため、動的生成 HTML の簡易整形・一括処理・ユーザー入力のサニタイズなど、コードを簡潔に保ちたい場面に最適です。エラーの詳細や DOM 走査が必要になったタイミングで tidy_parse_string() ベースの処理に移行するのが実践的な判断基準です。

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