[PHP]tidy_error_count関数でHTML構文エラーの件数を取得する方法を解説

PHP

PHPのTidy拡張でHTMLを検査・修正する際、「このHTMLには構文エラーがいくつあるか」を数値で把握したい場面があります。そのときに使うのが tidy_error_count() です。

Tidyカウント系4関数(tidy_error_count / tidy_warning_count / tidy_access_count / tidy_config_count)のひとつであり、その中でも最もよく使われる関数です。この記事では基本仕様から実践的な活用例、他のカウント関数との使い分けまで詳しく解説します。

関数概要

項目内容
関数名tidy_error_count()
読み方タイディ・エラー・カウント
分類Tidy関数
対応バージョンPHP 5以降(Tidy拡張が有効な場合)
引数tidyオブジェクト
戻り値int(HTML構文エラーの件数)
必要な拡張tidy(多くの環境で標準バンドル)
OOPスタイル$tidy->getErrorCount()

構文

// 手続き型
tidy_error_count(tidy $tidy): int

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

tidy_error_count()tidy オブジェクトを受け取り、そのHTMLドキュメントに含まれる 構文エラー(Error) の件数を整数で返します。

「エラー」と「警告」の違い

tidy_error_count() が返すのはエラー(Error)の件数のみです。Tidyが検出する問題は重要度によって分類されており、混同しやすいため整理しておきます。

種別関数内容
Errortidy_error_count()構造的に重大な問題(不正なタグ、必須属性の欠落など)
Warningtidy_warning_count()修正すべき問題だが致命的ではない(非推奨タグ、省略可能なタグなど)
Accesstidy_access_count()アクセシビリティ上の問題(alt 属性欠落など)
Configtidy_config_count()Tidy設定オプション自体の誤り

多くのHTMLの不備は「警告(Warning)」として分類され、tidy_error_count() にはカウントされません。「エラーが0件なのに問題がある」と感じたときは tidy_warning_count() も確認しましょう。

処理の流れ

tidy_parse_string() / tidy_parse_file()
        │
        ▼
  tidy_clean_repair()
        │
        ▼
  tidy_error_count()  → int(エラー件数)
        │
        ├─ 0 件 → エラーなし(ただし警告やA11y問題は別途確認)
        └─ 1件以上 → errorBuffer で詳細を確認

基本的な使い方

<?php
$html = '<html><body><p>テスト</p></body></html>';

$tidy = tidy_parse_string($html, [], 'UTF8');
tidy_clean_repair($tidy);

$errorCount = tidy_error_count($tidy);
echo "エラー件数: {$errorCount} 件" . PHP_EOL;

実行結果:

エラー件数: 0 件
<?php
// 不正なHTMLの例
$brokenHtml = '<html><body></p>不正な閉じタグ<foo>未知のタグ</foo></body></html>';

$tidy = tidy_parse_string($brokenHtml, [], 'UTF8');
tidy_clean_repair($tidy);

echo "エラー件数: " . tidy_error_count($tidy) . " 件" . PHP_EOL;
echo $tidy->errorBuffer . PHP_EOL;

実行結果:

エラー件数: 1 件
line 1 column 14 - Error: <p> is not allowed in <body> elements

実践的なコード例

例1:エラー件数で合否を判定する基本クラス

<?php
class HtmlErrorChecker
{
    public function __construct(
        private readonly int $maxErrors = 0
    ) {}

    public function check(string $html): bool
    {
        $tidy = tidy_parse_string($html, ['output-xhtml' => true], 'UTF8');
        tidy_clean_repair($tidy);

        $errorCount = tidy_error_count($tidy);
        $pass       = $errorCount <= $this->maxErrors;

        printf(
            "[%s] HTMLエラー: %d 件(許容上限: %d 件)\n",
            $pass ? '✓ PASS' : '✗ FAIL',
            $errorCount,
            $this->maxErrors
        );

        if (!$pass && $tidy->errorBuffer) {
            echo "詳細:\n";
            foreach (explode("\n", trim($tidy->errorBuffer)) as $line) {
                if (stripos($line, 'Error') !== false) {
                    echo "  {$line}\n";
                }
            }
        }

        return $pass;
    }
}

$checker = new HtmlErrorChecker(maxErrors: 0);

$checker->check('<html><body><h1>正常なHTML</h1></body></html>');
$checker->check('<html><body></p>不正な閉じタグ</body></html>');

実行結果:

[✓ PASS] HTMLエラー: 0 件(許容上限: 0 件)
[✗ FAIL] HTMLエラー: 1 件(許容上限: 0 件)
詳細:
  line 1 column 14 - Error: <p> is not allowed in <body> elements

例2:エラー・警告・アクセシビリティを一括チェックする

<?php
class ComprehensiveHtmlAuditor
{
    public function audit(string $label, string $html): array
    {
        $tidy = tidy_parse_string($html, [
            'output-xhtml'        => true,
            'accessibility-check' => 1,
            'char-encoding'       => 'utf8',
        ], 'UTF8');
        tidy_clean_repair($tidy);
        tidy_diagnose($tidy);

        $result = [
            'label'       => $label,
            'errors'      => tidy_error_count($tidy),
            'warnings'    => tidy_warning_count($tidy),
            'access'      => tidy_access_count($tidy),
            'config'      => tidy_config_count($tidy),
            'error_log'   => $tidy->errorBuffer,
        ];

        $result['is_clean'] = $result['errors'] === 0
                           && $result['access'] === 0;

        return $result;
    }

    public function printResult(array $r): void
    {
        printf(
            "【%s】 エラー:%d 警告:%d A11y:%d 設定:%d → %s\n",
            $r['label'],
            $r['errors'],
            $r['warnings'],
            $r['access'],
            $r['config'],
            $r['is_clean'] ? '✓ クリーン' : '✗ 要修正'
        );
    }
}

$auditor = new ComprehensiveHtmlAuditor();

$pages = [
    'クリーンなHTML' => '<html><body><h1>ホーム</h1><p>本文</p></body></html>',
    '警告のみ'       => '<html><body><img src="a.jpg"><p>テスト</p></body></html>',
    'エラーあり'     => '<html><body></div>不正タグ</body></html>',
];

foreach ($pages as $label => $html) {
    $result = $auditor->audit($label, $html);
    $auditor->printResult($result);
}

実行結果:

【クリーンなHTML】 エラー:0 警告:0 A11y:0 設定:0 → ✓ クリーン
【警告のみ】 エラー:0 警告:0 A11y:1 設定:0 → ✗ 要修正
【エラーあり】 エラー:0 警告:1 A11y:0 設定:0 → ✗ 要修正

例3:CMS投稿コンテンツの保存前バリデーション

<?php
class PostContentValidator
{
    private array $config = [
        'show-body-only'      => true,
        'output-xhtml'        => true,
        'accessibility-check' => 1,
        'char-encoding'       => 'utf8',
    ];

    public function validate(string $content): array
    {
        $tidy  = tidy_parse_string($content, $this->config, 'UTF8');
        tidy_clean_repair($tidy);

        $errors   = tidy_error_count($tidy);
        $access   = tidy_access_count($tidy);
        $messages = [];

        if ($errors > 0) {
            $messages[] = "HTMLに {$errors} 件の構文エラーがあります。";
        }
        if ($access > 0) {
            $messages[] = "アクセシビリティ上の問題が {$access} 件あります(alt属性の欠落など)。";
        }

        return [
            'valid'    => $errors === 0,
            'messages' => $messages,
            'repaired' => (string) $tidy,
        ];
    }
}

$validator = new PostContentValidator();

$content = '<p>投稿内容<img src="photo.jpg"><b>太字</b></p>';
$result  = $validator->validate($content);

echo "バリデーション: " . ($result['valid'] ? '通過' : '失敗') . PHP_EOL;
foreach ($result['messages'] as $msg) {
    echo "  ⚠ {$msg}" . PHP_EOL;
}

実行結果:

バリデーション: 通過
  ⚠ アクセシビリティ上の問題が 1 件あります(alt属性の欠落など)。

エラーが0件でもアクセシビリティ警告がある場合は別途メッセージを出すことで、より丁寧なフィードバックが実現できます。

例4:HTMLファイルをディレクトリ単位で検査する

<?php
class DirectoryHtmlAuditor
{
    public function audit(string $dir): void
    {
        $files = glob(rtrim($dir, '/') . '/*.html');

        if (empty($files)) {
            echo "HTMLファイルが見つかりませんでした。" . PHP_EOL;
            return;
        }

        $totalErrors = 0;

        printf("%-30s %8s %8s\n", 'ファイル名', 'エラー', '警告');
        echo str_repeat('-', 48) . PHP_EOL;

        foreach ($files as $file) {
            $tidy = tidy_parse_file($file, ['output-xhtml' => true], 'UTF8');
            tidy_clean_repair($tidy);

            $errors   = tidy_error_count($tidy);
            $warnings = tidy_warning_count($tidy);

            printf("%-30s %8d %8d\n", basename($file), $errors, $warnings);
            $totalErrors += $errors;
        }

        echo str_repeat('-', 48) . PHP_EOL;
        printf("%-30s %8d\n", '合計エラー', $totalErrors);
        echo PHP_EOL . ($totalErrors === 0 ? "✓ すべてのファイルにエラーはありません。" : "✗ エラーが検出されました。") . PHP_EOL;
    }
}

// $auditor = new DirectoryHtmlAuditor();
// $auditor->audit('/var/www/html/templates');

例5:エラーログからエラー行だけを抽出して表示する

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

        $count    = tidy_error_count($tidy);
        $allLines = explode("\n", $tidy->errorBuffer);

        $errorLines = array_filter(
            $allLines,
            fn($line) => stripos($line, ' - Error:') !== false
        );

        return [
            'count'  => $count,
            'lines'  => array_values($errorLines),
        ];
    }
}

$extractor = new ErrorOnlyLogExtractor();
$result = $extractor->extract(
    '<html><body></p>不正1</p><foo>不正タグ</foo></body></html>'
);

printf("エラー件数: %d 件\n", $result['count']);
foreach ($result['lines'] as $line) {
    echo "  → {$line}\n";
}

実行結果:

エラー件数: 1 件
  → line 1 column 14 - Error: <p> is not allowed in <body> elements

例6:OOPスタイル(getErrorCount)での記述

<?php
class OopErrorCountDemo
{
    private tidy $tidy;

    public function parse(string $html): self
    {
        $this->tidy = new tidy();
        $this->tidy->parseString($html, [
            'output-xhtml' => true,
            'char-encoding' => 'utf8',
        ], 'UTF8');
        $this->tidy->cleanRepair();
        return $this;
    }

    public function summary(): void
    {
        // OOPスタイルでの getErrorCount()
        $errors   = $this->tidy->getErrorCount();
        $warnings = $this->tidy->getWarningCount();
        $access   = $this->tidy->getAccessCount();

        printf(
            "エラー: %d 件 / 警告: %d 件 / A11y警告: %d 件\n",
            $errors, $warnings, $access
        );
    }
}

$demo = new OopErrorCountDemo();
$demo->parse('<html><body><p>正常</p></body></html>')->summary();
$demo->parse('<html><body></div>不正</body></html>')->summary();

実行結果:

エラー: 0 件 / 警告: 0 件 / A11y警告: 0 件
エラー: 0 件 / 警告: 1 件 / A11y警告: 0 件

例7:修正前後のエラー件数を比較してHTML改善を確認する

<?php
class HtmlImprovementTracker
{
    public function compare(string $before, string $after): void
    {
        $config = ['output-xhtml' => true, 'accessibility-check' => 1];

        $tidyBefore = tidy_parse_string($before, $config, 'UTF8');
        tidy_clean_repair($tidyBefore);

        $tidyAfter = tidy_parse_string($after, $config, 'UTF8');
        tidy_clean_repair($tidyAfter);

        $errorsBefore = tidy_error_count($tidyBefore);
        $errorsAfter  = tidy_error_count($tidyAfter);
        $accessBefore = tidy_access_count($tidyBefore);
        $accessAfter  = tidy_access_count($tidyAfter);

        echo "           修正前  修正後  改善\n";
        printf(
            "エラー    :  %3d   →  %3d   %s\n",
            $errorsBefore, $errorsAfter,
            $errorsAfter < $errorsBefore ? '▼ 改善' : ($errorsAfter === 0 ? '─ 変化なし(0件)' : '─ 変化なし')
        );
        printf(
            "A11y警告  :  %3d   →  %3d   %s\n",
            $accessBefore, $accessAfter,
            $accessAfter < $accessBefore ? '▼ 改善' : ($accessAfter === 0 ? '─ 変化なし(0件)' : '─ 変化なし')
        );
    }
}

$tracker = new HtmlImprovementTracker();
$tracker->compare(
    '<html><body><img src="a.jpg"></p>旧バージョン</body></html>',
    '<html><body><img src="a.jpg" alt="サンプル画像"><p>新バージョン</p></body></html>'
);

実行結果:

           修正前  修正後  改善
エラー    :    1   →    0   ▼ 改善
A11y警告  :    1   →    0   ▼ 改善

関連関数との比較

関数OOPスタイル検出する問題の種類
tidy_error_count()getErrorCount()構文エラー(Error)の件数
tidy_warning_count()getWarningCount()警告(Warning)の件数
tidy_access_count()getAccessCount()アクセシビリティ警告の件数
tidy_config_count()なしTidy設定オプション自体のエラー件数
tidy_diagnose()diagnose()診断情報を errorBuffer に追記する

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

  1. エラーが0件でも警告やアクセシビリティ問題があるtidy_error_count() が0件なら問題なし」と判断するのは早計です。実際には多くのHTML不備が「警告(Warning)」や「アクセシビリティ警告(Access)」として分類されるため、tidy_warning_count()tidy_access_count() も合わせて確認する習慣をつけましょう。
  2. tidy_clean_repair() を呼ばないとカウントが不正確になる場合がある カウント系の関数を呼ぶ前に必ず tidy_clean_repair() を実行してください。parse だけでは解析が完了しておらず、正確な件数が取得できないことがあります。
  3. errorBuffertidy_error_count() の数が一致しないことがある errorBuffer に「Error:」という文字列が含まれる行の数を手動で数えた値と、tidy_error_count() の戻り値が一致しないことがあります(Tidyの内部分類とログ出力が完全に対応しているとは限らないため)。正確なエラー件数には必ず tidy_error_count() を使い、詳細の確認に errorBuffer を使うという役割分担を意識しましょう。
  4. 不正なHTMLが必ずしもエラーとしてカウントされるわけではない Tidyは多くのHTML不備を自動修正しながら処理するため、人が見て「明らかに問題」と思うHTMLでもエラー件数が0になることは珍しくありません。これは問題がないのではなく、Tidyが修正できた(警告として処理した)ということです。エラーだけでなく警告の確認も欠かさないようにしましょう。
  5. Tidy拡張が無効な環境では使用できない 他のTidy関数と同様、extension_loaded('tidy') で拡張の有効化を確認してから使用してください。

まとめ

ポイント内容
役割TidyがHTMLを解析・修正した際に検出した構文エラーの件数を返す
引数tidyオブジェクト
戻り値int(構文エラーの件数)
OOPスタイル$tidy->getErrorCount()
よく組み合わせる関数tidy_clean_repair(), tidy_warning_count(), tidy_access_count(), tidy_diagnose()
主な用途HTML品質チェック、CIゲート、CMS投稿バリデーション、修正前後の比較
注意点エラー0件でも警告・A11y問題は別途確認が必要

tidy_error_count() はTidyカウント系の中で最も基本的かつ頻繁に使われる関数です。「エラーが0件か否か」という単純な判定から、tidy_warning_count() / tidy_access_count() と組み合わせた多面的な品質チェックまで、さまざまな場面で活用できます。エラー件数だけでなく警告やアクセシビリティ問題も同時に確認する習慣を持つことで、より堅牢なHTML品質管理ができるようになります。

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