[PHP]tidy_repair_file完全解説|HTMLファイルをワンステップで修復・整形して文字列として取得する方法

PHP

1. 関数概要

tidy_repair_file は、PHP の Tidy 拡張が提供する HTML/XHTML ファイルの読み込みから修復・整形までを一度に行い、結果を文字列で返す関数です。tidy_parse_file()tidy_clean_repair()tidy_get_output() の3ステップを1回の呼び出しにまとめたショートカット関数であり、tidy オブジェクトを介さず直接整形済み文字列を得られます。

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

2. 構文

tidy_repair_file(
    string $filename,
    array|string|null $config = null,
    ?string $encoding = null,
    bool $useIncludePath = false
): string|false

パラメータ

パラメータ説明
$filenamestring修復するファイルのパスまたは URL
$configarray|string|nullTidy オプションの連想配列、または tidyrc 設定ファイルのパス。null でデフォルト設定
$encodingstring|null入出力エンコーディング(例: 'UTF8', 'latin1')。null でデフォルト(ascii
$useIncludePathbooltrue にすると PHP の include_path からもファイルを検索する

戻り値

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

3. tidy_repair_file vs 3ステップ処理の比較

観点tidy_repair_file()3ステップ処理
コード量1行3行以上
tidy オブジェクト不要必要
ステータス取得❌ 不可tidy_get_status()
エラーバッファ取得❌ 不可tidy_get_error_buffer()
DOM ツリー走査❌ 不可tidy_get_root()
向いている用途結果文字列だけ欲しい場合詳細な検査・分析が必要な場合

4. 動作概念図

┌──────────────────────────────────────────────────────────┐
│  ファイルシステム / URL                                   │
│  /var/www/html/page.html                                  │
└─────────────────────┬────────────────────────────────────┘
                      │
                      ▼
┌──────────────────────────────────────────────────────────┐
│  tidy_repair_file($filename, $config, $encoding)          │
│                                                           │
│  内部で自動実行:                                          │
│  ① tidy_parse_file()   ファイル読み込み・解析            │
│  ② tidy_clean_repair() 修復・整形                        │
│  ③ tidy_get_output()   文字列化                          │
└─────────────────────┬────────────────────────────────────┘
                      │
          ┌───────────┴───────────┐
      成功 │                       │ 失敗
          ▼                       ▼
   整形済み HTML 文字列         false
   (string)

5. 基本的な使い方

<?php
// テスト用の不正な HTML ファイルを作成
$tmpFile = tempnam(sys_get_temp_dir(), 'tidy_') . '.html';
file_put_contents($tmpFile, '<HTML><BODY><H1>タイトル<P>本文<br>テキスト</BODY>');

// ワンステップで修復・整形した文字列を取得
$result = tidy_repair_file($tmpFile, ['indent' => true, 'wrap' => 80], 'UTF8');

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

unlink($tmpFile);

出力例:

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

6. 実践的なコード例

例1: ファイルを修復して別ファイルに保存するクラス

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

    public function repairToFile(string $inputPath, string $outputPath): bool
    {
        $result = tidy_repair_file($inputPath, $this->config, 'UTF8');

        if ($result === false) {
            echo "❌ 修復失敗: {$inputPath}" . PHP_EOL;
            return false;
        }

        $bytes = file_put_contents($outputPath, $result);
        if ($bytes === false) {
            echo "❌ 書き込み失敗: {$outputPath}" . PHP_EOL;
            return false;
        }

        echo "✅ 修復完了: {$outputPath} ({$bytes} bytes)" . PHP_EOL;
        return true;
    }

    public function repairInPlace(string $filepath): bool
    {
        return $this->repairToFile($filepath, $filepath);
    }
}

$inputFile  = tempnam(sys_get_temp_dir(), 'tidy_in_')  . '.html';
$outputFile = tempnam(sys_get_temp_dir(), 'tidy_out_') . '.html';
file_put_contents($inputFile, '<html><body><p>未閉じ<b>太字</body></html>');

$repairer = new HtmlFileRepairer();
$repairer->repairToFile($inputFile, $outputFile);

echo file_get_contents($outputFile);
unlink($inputFile);
unlink($outputFile);

出力例:

✅ 修復完了: /tmp/tidy_out_XXXX.html (212 bytes)
<!DOCTYPE html>
<html>
<head>
  <title></title>
</head>
<body>
  <p>未閉じ<b>太字</b></p>
</body>
</html>

例2: ファイルの存在確認とエラーハンドリングを備えたクラス

<?php
class SafeHtmlFileRepairer
{
    private string $lastError = '';

    public function repair(
        string $filepath,
        array  $config   = ['indent' => true],
        string $encoding = 'UTF8'
    ): string|false {
        if (!file_exists($filepath)) {
            $this->lastError = "ファイルが存在しません: {$filepath}";
            return false;
        }

        if (!is_readable($filepath)) {
            $this->lastError = "ファイルを読み取れません: {$filepath}";
            return false;
        }

        $result = tidy_repair_file($filepath, $config, $encoding);

        if ($result === false) {
            $this->lastError = "Tidy による修復に失敗しました: {$filepath}";
            return false;
        }

        return $result;
    }

    public function getLastError(): string
    {
        return $this->lastError;
    }
}

$repairer = new SafeHtmlFileRepairer();

// 存在しないファイル
$result = $repairer->repair('/nonexistent/page.html');
if ($result === false) {
    echo "❌ " . $repairer->getLastError() . PHP_EOL;
}

// 正常なファイル
$tmpFile = tempnam(sys_get_temp_dir(), 'tidy_') . '.html';
file_put_contents($tmpFile, '<html><body><p>テスト</p></body></html>');

$result = $repairer->repair($tmpFile);
if ($result !== false) {
    echo "✅ 修復成功 (" . strlen($result) . " bytes)" . PHP_EOL;
    echo $result;
}
unlink($tmpFile);

出力例:

❌ ファイルが存在しません: /nonexistent/page.html
✅ 修復成功 (183 bytes)
<!DOCTYPE html>
<html>
<head>
  <title></title>
</head>
<body>
  <p>テスト</p>
</body>
</html>

例3: ディレクトリ内の HTML ファイルを一括修復するクラス

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

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

    public function repairAll(string $inputDir, string $outputDir): void
    {
        if (!is_dir($outputDir)) {
            mkdir($outputDir, 0755, true);
        }

        $files = glob($inputDir . '/*.html') ?: [];
        echo "対象ファイル数: " . count($files) . " 件" . PHP_EOL . PHP_EOL;

        foreach ($files as $inputPath) {
            $basename   = basename($inputPath);
            $outputPath = $outputDir . DIRECTORY_SEPARATOR . $basename;

            $result = tidy_repair_file($inputPath, $this->config, 'UTF8');

            if ($result === false) {
                echo "❌ 失敗: {$basename}" . PHP_EOL;
                $this->failCount++;
                continue;
            }

            file_put_contents($outputPath, $result);
            printf("✅ %-20s → %d bytes%s", $basename, strlen($result), PHP_EOL);
            $this->successCount++;
        }

        echo PHP_EOL . "完了 — 成功: {$this->successCount} / 失敗: {$this->failCount}" . PHP_EOL;
    }
}

// テスト用ファイルを作成
$inDir  = sys_get_temp_dir() . '/tidy_repair_in';
$outDir = sys_get_temp_dir() . '/tidy_repair_out';
@mkdir($inDir);

file_put_contents("{$inDir}/index.html",   '<html><body><h1>トップ</h1><p>本文</body>');
file_put_contents("{$inDir}/about.html",   '<HTML><BODY><H2>概要</H2><P>テキスト</BODY>');
file_put_contents("{$inDir}/contact.html", '<html><body><form><input type=text></form></body></html>');

$repairer = new HtmlDirectoryRepairer();
$repairer->repairAll($inDir, $outDir);

// クリーンアップ
array_map('unlink', glob("{$inDir}/*.html")  ?: []);
array_map('unlink', glob("{$outDir}/*.html") ?: []);
rmdir($inDir);
rmdir($outDir);

出力例:

対象ファイル数: 3 件

✅ about.html         → 198 bytes
✅ contact.html       → 215 bytes
✅ index.html         → 195 bytes

完了 — 成功: 3 / 失敗: 0

例4: 修復前後のバイト数を比較表示するクラス

<?php
class HtmlRepairDiffReporter
{
    public function report(string $filepath, array $config = ['indent' => true]): void
    {
        if (!file_exists($filepath)) {
            echo "❌ ファイルが存在しません: {$filepath}" . PHP_EOL;
            return;
        }

        $original = file_get_contents($filepath);
        $repaired = tidy_repair_file($filepath, $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("%-12s: %6d bytes / %3d 行%s", "修復前", strlen($original), $origLines,    PHP_EOL);
        printf("%-12s: %6d bytes / %3d 行%s", "修復後", strlen($repaired), $repairedLines, PHP_EOL);
        printf("%-12s: %+d bytes%s", "差分",
            strlen($repaired) - strlen($original), PHP_EOL);

        echo PHP_EOL . "--- 修復後 ---" . PHP_EOL;
        echo $repaired;
    }
}

$tmpFile = tempnam(sys_get_temp_dir(), 'tidy_') . '.html';
file_put_contents($tmpFile, '<html><body><p>段落<b>太字</body>');

$reporter = new HtmlRepairDiffReporter();
$reporter->report($tmpFile);
unlink($tmpFile);

出力例:

=== 修復前後の比較 ===
修復前      :     33 bytes /   1 行
修復後      :    212 bytes /  13 行
差分        : +179 bytes

--- 修復後 ---
<!DOCTYPE html>
<html>
<head>
  <title></title>
</head>
<body>
  <p>段落<b>太字</b></p>
</body>
</html>

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

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

    public function convert(string $inputPath, string $outputPath): bool
    {
        $result = tidy_repair_file($inputPath, $this->config, 'UTF8');

        if ($result === false) {
            echo "❌ 変換失敗: {$inputPath}" . PHP_EOL;
            return false;
        }

        file_put_contents($outputPath, $result);
        $isXhtml = str_contains($result, 'xmlns="http://www.w3.org/1999/xhtml"');
        echo "✅ XHTML 変換: " . ($isXhtml ? '成功' : '未確認') . " → {$outputPath}" . PHP_EOL;
        return true;
    }
}

$inputFile  = tempnam(sys_get_temp_dir(), 'tidy_in_')  . '.html';
$outputFile = tempnam(sys_get_temp_dir(), 'tidy_out_') . '.xhtml';
file_put_contents($inputFile, '<html><body><br><img src="photo.jpg"></body></html>');

$converter = new XhtmlFileConverter();
$converter->convert($inputFile, $outputFile);
echo file_get_contents($outputFile);

unlink($inputFile);
unlink($outputFile);

出力例:

✅ XHTML 変換: 成功 → /tmp/tidy_out_XXXX.xhtml
<?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="" />
</body>
</html>

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

<?php
class TidyRcBatchRepairer
{
    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));
    }

    /**
     * @param string[] $filepaths
     * @return array<string, string|false>
     */
    public function repairAll(array $filepaths): array
    {
        $results = [];
        foreach ($filepaths as $path) {
            // $config に設定ファイルパス(string)を渡す
            $results[basename($path)] = tidy_repair_file($path, $this->rcPath, 'UTF8');
        }
        return $results;
    }

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

// テスト用ファイルを作成
$files = [];
foreach (['page1' => '<html><body><p>A</body>', 'page2' => '<HTML><BODY><P>B</BODY>'] as $name => $html) {
    $path = sys_get_temp_dir() . "/{$name}.html";
    file_put_contents($path, $html);
    $files[] = $path;
}

$repairer = new TidyRcBatchRepairer([
    'indent' => true,
    'wrap'   => 100,
    'quiet'  => true,
]);

$results = $repairer->repairAll($files);
foreach ($results as $name => $output) {
    echo "=== {$name} ===" . PHP_EOL;
    echo $output !== false ? $output : "❌ 失敗" . PHP_EOL;
}

foreach ($files as $f) { unlink($f); }

出力例:

=== page1.html ===
<!DOCTYPE html>
<html>
<head>
  <title></title>
</head>
<body>
  <p>A</p>
</body>
</html>

=== page2.html ===
<!DOCTYPE html>
<html>
<head>
  <title></title>
</head>
<body>
  <p>B</p>
</body>
</html>

例7: 修復結果を JSON レポートとして出力するクラス

<?php
class HtmlFileRepairReporter
{
    /**
     * @param array<string, string> $files  キー: ラベル / 値: ファイルパス
     * @return array<string, mixed>
     */
    public function report(array $files, array $config = ['indent' => true]): array
    {
        $report = [];

        foreach ($files as $label => $path) {
            $originalSize = file_exists($path) ? filesize($path) : null;
            $result       = tidy_repair_file($path, $config, 'UTF8');

            $report[$label] = [
                'path'          => $path,
                'original_bytes'=> $originalSize,
                'repaired_bytes'=> $result !== false ? strlen($result) : null,
                'success'       => $result !== false,
                'size_diff'     => $result !== false && $originalSize !== null
                                    ? strlen($result) - $originalSize
                                    : null,
            ];
        }

        return $report;
    }
}

// テスト用ファイルを作成
$tmpFiles = [];
$defs = [
    '正常な HTML'   => '<!DOCTYPE html><html><head><title>T</title></head><body><p>OK</p></body></html>',
    '不完全な HTML' => '<html><body><table><td>セル</body>',
    '大文字タグ'    => '<HTML><BODY><H1>見出し</H1><P>本文</P></BODY></HTML>',
];
foreach ($defs as $label => $html) {
    $path = tempnam(sys_get_temp_dir(), 'tidy_') . '.html';
    file_put_contents($path, $html);
    $tmpFiles[$label] = $path;
}

$reporter = new HtmlFileRepairReporter();
$report   = $reporter->report($tmpFiles);
echo json_encode($report, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);

foreach ($tmpFiles as $path) { unlink($path); }

出力例:

{
    "正常な HTML": {
        "path": "/tmp/tidyXXXX.html",
        "original_bytes": 87,
        "repaired_bytes": 183,
        "success": true,
        "size_diff": 96
    },
    "不完全な HTML": {
        "path": "/tmp/tidyXXXX.html",
        "original_bytes": 35,
        "repaired_bytes": 219,
        "success": true,
        "size_diff": 184
    },
    "大文字タグ": {
        "path": "/tmp/tidyXXXX.html",
        "original_bytes": 54,
        "repaired_bytes": 195,
        "success": true,
        "size_diff": 141
    }
}

7. 関連関数との比較

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

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


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

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

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

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

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

② ファイルの上書きは戻り値を file_put_contents() で書き込む

tidy_repair_file() は修復後の文字列を返すだけでファイルを自動上書きしません。元のファイルを修復済み内容に置き換えたい場合は file_put_contents() と組み合わせてください。

// 元ファイルをその場で修復・上書き
$repaired = tidy_repair_file($path, ['indent' => true], 'UTF8');
if ($repaired !== false) {
    file_put_contents($path, $repaired);
}

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

'UTF-8' ではなく 'UTF8''ISO-8859-1' ではなく 'latin1' のように Tidy 固有の表記が必要です。

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

④ OOP 版は存在しない

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

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

⑤ URL を渡す場合は allow_url_fopen が必要

$filename に URL を指定するには php.iniallow_url_fopen = On が必要です。


9. まとめ

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

tidy_repair_file() は「修復済みの HTML 文字列さえ得られればよい」場面でコードを大幅に簡略化できる便利なショートカット関数です。エラー検査や DOM 走査が不要なバッチ整形・ファイル変換処理に積極的に活用してください。詳細な解析が必要になった場合は tidy_parse_file() ベースの3ステップ処理へ移行する判断基準も覚えておくと便利です。

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