[PHP]tidy_parse_file完全解説|HTMLファイルをTidyで読み込み解析・修復・整形する方法

PHP

1. 関数概要

tidy_parse_file は、PHP の Tidy 拡張が提供する HTML/XHTML/XML ファイルを直接読み込んで解析する関数です。tidy_parse_string() が文字列を受け取るのに対し、tidy_parse_file() はファイルパスまたは URL を受け取り、その内容を解析した tidy オブジェクトを返します。ファイルベースの HTML 処理・バッチ変換・静的サイト整形などに活用できます。

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

2. 構文

// 手続き型
tidy_parse_file(
    string $filename,
    array|string|null $config = null,
    ?string $encoding = null,
    bool $useIncludePath = false
): tidy|false

// オブジェクト指向型
$tidy->parseFile(
    string $filename,
    array|string|null $config = null,
    ?string $encoding = null,
    bool $useIncludePath = false
): bool

パラメータ

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

戻り値

  • 手続き型: 成功時は tidy オブジェクト、失敗時は false
  • OOP 型: 成功時は true、失敗時は false

3. tidy_parse_file vs tidy_parse_string 比較

項目tidy_parse_file()tidy_parse_string()
入力ファイルパス / URLHTML 文字列
主な用途ファイルベースの処理・バッチ変換動的生成HTMLの処理
URL 対応✅(allow_url_fopen が有効な場合)
include_path 対応✅($useIncludePath=true
OOP 戻り値booltidy|false

4. 動作概念図

┌──────────────────────────────────────────────────────────┐
│  ファイルシステム / URL                                   │
│  /var/www/html/page.html                                  │
│  https://example.com/page.html                            │
└─────────────────────┬────────────────────────────────────┘
                      │ tidy_parse_file($filename, $config, $encoding)
                      ▼
┌──────────────────────────────────────────────────────────┐
│  ファイル読み込み                                         │
│  (存在確認・パーミッション・エンコード変換)             │
└─────────────────────┬────────────────────────────────────┘
                      │
                      ▼
┌──────────────────────────────────────────────────────────┐
│  Tidy 解析エンジン                                        │
│  ・DOCTYPE 検出     ・構文エラー収集                      │
│  ・内部ツリー構築   ・エンコード正規化                    │
└─────────────────────┬────────────────────────────────────┘
                      │
          ┌───────────┴───────────┐
          │ 成功                  │ 失敗
          ▼                       ▼
    tidy オブジェクト           false
    (以降の Tidy 関数で利用可)

5. 基本的な使い方

<?php
// テスト用 HTML ファイルを作成
$tmpFile = tempnam(sys_get_temp_dir(), 'tidy_') . '.html';
file_put_contents($tmpFile, '<html><body><p>ファイル読み込みテスト<br></body></html>');

$tidy = tidy_parse_file($tmpFile, ['indent' => true, 'wrap' => 80], 'UTF8');

if ($tidy !== false) {
    tidy_clean_repair($tidy);
    echo tidy_get_output($tidy);
} else {
    echo "ファイルの解析に失敗しました。" . PHP_EOL;
}

unlink($tmpFile);

出力例:

<!DOCTYPE html>
<html>
<head>
  <title></title>
</head>
<body>
  <p>ファイル読み込みテスト<br></p>
</body>
</html>

6. 実践的なコード例

例1: ファイルを読み込んで整形・保存するクラス

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

    public function formatFile(string $inputPath, string $outputPath): bool
    {
        $tidy = tidy_parse_file($inputPath, $this->config, 'UTF8');

        if ($tidy === false) {
            echo "❌ ファイルの読み込みに失敗: {$inputPath}" . PHP_EOL;
            return false;
        }

        tidy_clean_repair($tidy);
        $result = file_put_contents($outputPath, tidy_get_output($tidy));

        if ($result !== false) {
            echo "✅ 整形完了: {$outputPath} ({$result} bytes)" . PHP_EOL;
            return true;
        }

        echo "❌ ファイルの書き込みに失敗: {$outputPath}" . PHP_EOL;
        return false;
    }
}

// テスト用ファイル作成
$input  = tempnam(sys_get_temp_dir(), 'tidy_in_')  . '.html';
$output = tempnam(sys_get_temp_dir(), 'tidy_out_') . '.html';
file_put_contents($input, '<HTML><BODY><H1>タイトル<P>本文テキスト</BODY>');

$formatter = new HtmlFileFormatter();
$formatter->formatFile($input, $output);

echo file_get_contents($output);
unlink($input);
unlink($output);

出力例:

✅ 整形完了: /tmp/tidy_out_XXXX.html (198 bytes)
<!DOCTYPE html>
<html>
<head>
  <title></title>
</head>
<body>
  <h1>タイトル</h1>
  <p>本文テキスト</p>
</body>
</html>

例2: OOP スタイルで parseFile() を使う

<?php
class TidyOopFileParser
{
    private tidy $tidy;

    public function __construct()
    {
        $this->tidy = new tidy();
    }

    public function parse(string $filepath, array $config = []): bool
    {
        $result = $this->tidy->parseFile($filepath, $config, 'UTF8');
        if ($result) {
            $this->tidy->cleanRepair();
        }
        return $result;
    }

    public function getOutput(): string
    {
        return $this->tidy->value;
    }

    public function getStatus(): int
    {
        return $this->tidy->getStatus();
    }

    public function isXhtml(): bool
    {
        return $this->tidy->isXhtml();
    }
}

$tmpFile = tempnam(sys_get_temp_dir(), 'tidy_') . '.html';
file_put_contents($tmpFile, '<!DOCTYPE html><html><head><title>OOP テスト</title></head><body><p>段落</p></body></html>');

$parser = new TidyOopFileParser();
if ($parser->parse($tmpFile, ['indent' => true])) {
    echo "ステータス : " . $parser->getStatus() . PHP_EOL;
    echo "XHTML 判定: " . var_export($parser->isXhtml(), true) . PHP_EOL;
    echo $parser->getOutput();
}
unlink($tmpFile);

出力例:

ステータス : 0
XHTML 判定: false
<!DOCTYPE html>
<html>
<head>
  <title>OOP テスト</title>
</head>
<body>
  <p>段落</p>
</body>
</html>

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

<?php
class SafeHtmlFileParser
{
    private ?tidy   $tidy      = null;
    private string  $lastError = '';

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

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

        $tidy = tidy_parse_file($filepath, $config, 'UTF8');
        if ($tidy === false) {
            $this->lastError = "Tidy による解析に失敗しました: {$filepath}";
            return false;
        }

        tidy_clean_repair($tidy);
        $this->tidy = $tidy;
        return true;
    }

    public function getOutput(): string
    {
        return $this->tidy !== null ? tidy_get_output($this->tidy) : '';
    }

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

$parser = new SafeHtmlFileParser();

// 存在しないファイル
if (!$parser->parse('/nonexistent/page.html')) {
    echo "❌ " . $parser->getLastError() . PHP_EOL;
}

// 正常なファイル
$tmpFile = tempnam(sys_get_temp_dir(), 'tidy_') . '.html';
file_put_contents($tmpFile, '<html><body><p>正常</p></body></html>');
if ($parser->parse($tmpFile, ['indent' => true])) {
    echo "✅ 解析成功" . PHP_EOL;
    echo $parser->getOutput();
}
unlink($tmpFile);

出力例:

❌ ファイルが存在しません: /nonexistent/page.html
✅ 解析成功
<!DOCTYPE html>
<html>
<head>
  <title></title>
</head>
<body>
  <p>正常</p>
</body>
</html>

例4: ディレクトリ内の HTML ファイルを一括整形するクラス

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

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

    public function formatAll(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 . '/' . $basename;

            $tidy = tidy_parse_file($inputPath, $this->config, 'UTF8');
            if ($tidy === false) {
                echo "❌ 失敗: {$basename}" . PHP_EOL;
                $this->failCount++;
                continue;
            }

            tidy_clean_repair($tidy);
            file_put_contents($outputPath, tidy_get_output($tidy));
            $status = tidy_get_status($tidy);
            $icon   = match($status) { 0 => '✅', 1 => '⚠️ ', default => '❌' };
            echo "{$icon} {$basename} (status={$status})" . PHP_EOL;
            $this->successCount++;
        }

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

// テスト用ディレクトリとファイルを用意
$inDir  = sys_get_temp_dir() . '/tidy_in';
$outDir = sys_get_temp_dir() . '/tidy_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>');

$formatter = new HtmlDirectoryFormatter();
$formatter->formatAll($inDir, $outDir);

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

出力例:

対象ファイル数: 3

✅ about.html (status=0)
✅ contact.html (status=0)
✅ index.html (status=0)

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

例5: 解析結果のステータスとエラーをレポートするクラス

<?php
class HtmlFileValidator
{
    public function validate(string $filepath): void
    {
        echo "=== {$filepath} ===" . PHP_EOL;

        $tidy = tidy_parse_file($filepath, [], 'UTF8');
        if ($tidy === false) {
            echo "❌ ファイルの解析に失敗しました。" . PHP_EOL;
            return;
        }

        $status = tidy_get_status($tidy);
        $errors = tidy_get_error_buffer($tidy) ?: '(なし)';

        echo "ステータス     : " . match($status) {
            0 => '✅ 正常', 1 => '⚠️  警告あり', 2 => '❌ エラーあり', default => '不明'
        } . PHP_EOL;
        echo "XHTML 判定     : " . (tidy_is_xhtml($tidy) ? 'true' : 'false') . PHP_EOL;
        echo "XML 判定       : " . (tidy_is_xml($tidy)   ? 'true' : 'false') . PHP_EOL;
        echo "HTML バージョン: " . tidy_get_html_ver($tidy) . PHP_EOL;
        echo "エラーバッファ : " . $errors . PHP_EOL;
    }
}

$tmpFile = tempnam(sys_get_temp_dir(), 'tidy_') . '.html';
file_put_contents($tmpFile, '<html><body><table><td>セルだけ</td></table></body></html>');

$validator = new HtmlFileValidator();
$validator->validate($tmpFile);
unlink($tmpFile);

出力例:

=== /tmp/tidy_XXXX.html ===
ステータス     : ⚠️  警告あり
XHTML 判定     : false
XML 判定       : false
HTML バージョン: 0
エラーバッファ : line 1 column 14 - Warning: <table> lacks "summary" attribute
...

例6: 設定ファイル(tidyrc)を使って解析するクラス

<?php
class TidyRcFileParser
{
    private string $rcPath;

    public function __construct(array $options = [])
    {
        // 一時的な tidyrc ファイルを生成
        $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 parse(string $htmlFile, string $encoding = 'UTF8'): tidy|false
    {
        // $config に設定ファイルパス(string)を渡す
        return tidy_parse_file($htmlFile, $this->rcPath, $encoding);
    }

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

$tmpHtml = tempnam(sys_get_temp_dir(), 'tidy_') . '.html';
file_put_contents($tmpHtml, '<html><body><p>tidyrc テスト</p></body></html>');

$parser = new TidyRcFileParser([
    'indent'       => true,
    'wrap'         => 100,
    'output-xhtml' => false,
]);

$tidy = $parser->parse($tmpHtml);
if ($tidy !== false) {
    tidy_clean_repair($tidy);
    echo "indent オプション: " . var_export(tidy_getopt($tidy, 'indent'), true) . PHP_EOL;
    echo "wrap オプション  : " . tidy_getopt($tidy, 'wrap')                     . PHP_EOL;
    echo tidy_get_output($tidy);
}
unlink($tmpHtml);

出力例:

indent オプション: true
wrap オプション  : 100
<!DOCTYPE html>
<html>
<head>
  <title></title>
</head>
<body>
  <p>tidyrc テスト</p>
</body>
</html>

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

<?php
class HtmlFileProfiler
{
    /**
     * @param string[] $filepaths
     * @return array<string, mixed>
     */
    public function profileAll(array $filepaths): array
    {
        $report = [];

        foreach ($filepaths as $path) {
            $basename = basename($path);
            $tidy     = tidy_parse_file($path, ['indent' => true], 'UTF8');

            if ($tidy === false) {
                $report[$basename] = ['error' => 'parse failed'];
                continue;
            }

            tidy_clean_repair($tidy);
            $report[$basename] = [
                'status'       => tidy_get_status($tidy),
                'is_xhtml'     => tidy_is_xhtml($tidy),
                'is_xml'       => tidy_is_xml($tidy),
                'html_ver'     => tidy_get_html_ver($tidy),
                'output_bytes' => strlen(tidy_get_output($tidy)),
                'error_buffer' => tidy_get_error_buffer($tidy) ?: null,
            ];
        }

        return $report;
    }
}

// テスト用ファイルを複数作成
$files = [];
$defs  = [
    'page1.html' => '<!DOCTYPE html><html><head><title>P1</title></head><body><p>ページ1</p></body></html>',
    'page2.html' => '<html><body><table><td>警告</td></table></body></html>',
    'page3.html' => '<?xml version="1.0"?><!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>X</title></head><body><p>XHTML</p></body></html>',
];

foreach ($defs as $name => $html) {
    $path = sys_get_temp_dir() . '/' . $name;
    file_put_contents($path, $html);
    $files[] = $path;
}

$profiler = new HtmlFileProfiler();
$report   = $profiler->profileAll($files);
echo json_encode($report, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);

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

出力例:

{
    "page1.html": {
        "status": 0,
        "is_xhtml": false,
        "is_xml": false,
        "html_ver": 0,
        "output_bytes": 183,
        "error_buffer": null
    },
    "page2.html": {
        "status": 1,
        "is_xhtml": false,
        "is_xml": false,
        "html_ver": 0,
        "output_bytes": 210,
        "error_buffer": "line 1 column 14 - Warning: ..."
    },
    "page3.html": {
        "status": 0,
        "is_xhtml": true,
        "is_xml": false,
        "html_ver": 0,
        "output_bytes": 312,
        "error_buffer": null
    }
}

7. 関連関数との比較

関数名入力用途戻り値
tidy_parse_file()ファイルパス / URLファイルを直接読み込んで解析tidy|false
tidy_parse_string()HTML 文字列動的生成 HTML・メモリ上の文字列を解析tidy|false
tidy_clean_repair()tidy オブジェクト解析済みドキュメントを修復・整形bool
tidy_get_output()tidy オブジェクト整形済み HTML を文字列で取得string
tidy_get_status()tidy オブジェクト解析ステータスを取得int
tidy_get_error_buffer()tidy オブジェクトエラーバッファを取得string|false

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

① ファイルが存在しない・読めない場合は false が返る

tidy_parse_file() はファイルの存在確認を行いません。パスが誤っている・パーミッション不足の場合は false が返るため、必ず戻り値を確認してください。

// NG: 戻り値を確認しない
$tidy = tidy_parse_file('/nonexistent.html');
tidy_clean_repair($tidy); // false に対して呼び出すとエラー

// OK: 戻り値を確認してから使う
$tidy = tidy_parse_file('/path/to/file.html', [], 'UTF8');
if ($tidy !== false) {
    tidy_clean_repair($tidy);
    echo tidy_get_output($tidy);
}

② OOP 版の戻り値は bool(tidy オブジェクトではない)

手続き型は tidy|false を返しますが、OOP 版 $tidy->parseFile()bool を返します。インスタンス自体($tidy)が解析結果を保持するため、戻り値の扱いに注意してください。

// 手続き型: 戻り値が tidy オブジェクト
$tidy = tidy_parse_file('page.html', [], 'UTF8');

// OOP 型: 戻り値は bool、オブジェクトは $tidy 自身
$tidy = new tidy();
$ok   = $tidy->parseFile('page.html', [], 'UTF8'); // bool
if ($ok) { $tidy->cleanRepair(); }

③ tidy_clean_repair() を忘れると未修復のまま出力される

tidy_parse_file() だけでは修復が行われません。tidy_get_output() で取得する前に必ず tidy_clean_repair() を呼んでください。

④ 文字エンコードの指定は第3引数で行う

$encoding パラメータは Tidy の内部エンコーディング名を使います。'UTF-8' ではなく 'UTF8''ISO-8859-1' ではなく 'latin1' のように Tidy 固有の表記が必要です。

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

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

$filename に URL を指定するには php.iniallow_url_fopen = On が必要です。セキュリティ上の理由で無効化されている環境では使えません。


9. まとめ

項目内容
主な用途HTML/XHTML/XML ファイルを直接読み込んで Tidy で解析する
戻り値(手続き型)tidy オブジェクト(成功)/ false(失敗)
戻り値(OOP 型)bool(成功/失敗)
tidy_parse_string() との違いファイルパス/URL を受け取る・include_path 対応
必須の後続処理tidy_clean_repair()tidy_get_output()
エンコーディング指定'UTF8', 'latin1' など Tidy 固有の表記を使う
URL 対応allow_url_fopen = On が必要

tidy_parse_file()tidy_parse_string() と対をなす関数で、ファイルベースの HTML 処理において最初に呼び出す関数です。tidy_parse_file()tidy_clean_repair()tidy_get_output() の3ステップを基本パターンとして押さえ、バッチ整形・バリデーション・JSON レポート生成など幅広い用途に応用してください。

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