[PHP]tidy_warning_count完全解説|HTML解析の警告件数を取得してバリデーション品質を定量化する方法

PHP

1. 関数概要

tidy_warning_count は、PHP の Tidy 拡張が HTML/XHTML を解析した際に検出した警告(Warning)の件数を整数で返す関数です。tidy_get_status() が「警告があるかどうか」を判定するのに対し、tidy_warning_count() は「何件の警告があるか」という数量を把握できます。品質スコアの算出・しきい値チェック・レポート生成に活用できます。

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

2. 構文

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

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

パラメータ

パラメータ説明
$tidytidytidy_parse_string() 等で生成した tidy オブジェクト

戻り値

解析中に検出された警告の件数(int)。警告がない場合は 0 を返します。


3. tidy_warning_count / tidy_error_count / tidy_get_status の関係

関数取得できる情報戻り値
tidy_warning_count()警告の件数int
tidy_error_count()エラーの件数int
tidy_access_count()アクセシビリティ警告の件数int
tidy_get_status()問題の有無を示すステータスコード(0/1/2)int
tidy_get_error_buffer()警告・エラーの詳細メッセージstring|false

使い分けのポイント: 「問題があるか」の二択判定は tidy_get_status()、「何件あるか」の数量把握は tidy_warning_count() / tidy_error_count()、「どんな問題か」の詳細確認は tidy_get_error_buffer() を使います。


4. 動作概念図

┌──────────────────────────────────────────────────────────┐
│  HTML ソース(問題あり)                                  │
│  <html><body><img src="a.jpg">                           │
│              <table><td>データ</td></table>               │
│  </body></html>                                           │
└─────────────────────┬────────────────────────────────────┘
                      │ tidy_parse_string()
                      ▼
┌──────────────────────────────────────────────────────────┐
│  Tidy 解析エンジン                                        │
│  ┌──────────────────────────────────────────────────┐    │
│  │ 内部カウンタ                                      │    │
│  │  warnings : 2  ← img の alt 欠落, table summary  │    │
│  │  errors   : 0                                    │    │
│  │  access   : 0                                    │    │
│  └──────────────────────────────────────────────────┘    │
└──────────┬────────────────────────┬───────────────────────┘
           │ tidy_warning_count()   │ tidy_get_status()
           ▼                        ▼
          2 (int)                  1 (警告あり)

5. 基本的な使い方

<?php
// 警告が発生しやすい HTML
$html = '<html><body>
  <img src="photo.jpg">
  <table><td>データ</td></table>
</body></html>';

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

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

echo "警告件数  : {$warnings}" . PHP_EOL;
echo "エラー件数: {$errors}"   . PHP_EOL;
echo "ステータス: {$status}"   . PHP_EOL;

if ($warnings > 0) {
    echo PHP_EOL . "--- 詳細 ---" . PHP_EOL;
    echo tidy_get_error_buffer($tidy) . PHP_EOL;
}

出力例:

警告件数  : 2
エラー件数: 0
ステータス: 1

--- 詳細 ---
line 2 column 3 - Warning: <img> lacks "alt" attribute
line 3 column 3 - Warning: <table> lacks "summary" attribute

6. 実践的なコード例

例1: 警告・エラーをまとめてレポートするクラス

<?php
class HtmlQualityReporter
{
    private tidy   $tidy;
    private int    $warnings;
    private int    $errors;
    private int    $access;
    private string $errorBuffer;

    public function __construct(string $html, array $config = [])
    {
        $this->tidy        = tidy_parse_string($html, $config, 'UTF8');
        $this->warnings    = tidy_warning_count($this->tidy);
        $this->errors      = tidy_error_count($this->tidy);
        $this->access      = tidy_access_count($this->tidy);
        $this->errorBuffer = tidy_get_error_buffer($this->tidy) ?: '';
    }

    public function getWarningCount(): int { return $this->warnings; }
    public function getErrorCount(): int   { return $this->errors; }
    public function getAccessCount(): int  { return $this->access; }

    public function report(): void
    {
        $status = tidy_get_status($this->tidy);
        $icon   = match($status) { 0 => '✅', 1 => '⚠️ ', 2 => '❌', default => '?' };

        echo "=== HTML 品質レポート ===" . PHP_EOL;
        echo "{$icon} ステータス            : {$status}" . PHP_EOL;
        echo "   警告件数              : {$this->warnings}" . PHP_EOL;
        echo "   エラー件数            : {$this->errors}"   . PHP_EOL;
        echo "   アクセシビリティ警告  : {$this->access}"   . PHP_EOL;

        if ($this->errorBuffer !== '') {
            echo PHP_EOL . "--- 詳細メッセージ ---" . PHP_EOL;
            echo $this->errorBuffer . PHP_EOL;
        }
    }
}

$html = '<html><body>
  <img src="photo.jpg">
  <table><td>セル</td></table>
  <p>本文<b>太字</b></p>
</body></html>';

$reporter = new HtmlQualityReporter($html);
$reporter->report();

出力例:

=== HTML 品質レポート ===
⚠️  ステータス            : 1
   警告件数              : 2
   エラー件数            : 0
   アクセシビリティ警告  : 0

--- 詳細メッセージ ---
line 2 column 3 - Warning: <img> lacks "alt" attribute
line 3 column 3 - Warning: <table> lacks "summary" attribute

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

<?php
class TidyOopWarningChecker
{
    private tidy $tidy;

    public function __construct(string $html, array $config = [])
    {
        $this->tidy = new tidy();
        $this->tidy->parseString($html, $config, 'UTF8');
    }

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

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

    public function describe(): void
    {
        $w = $this->getWarningCount();
        $e = $this->getErrorCount();
        echo "warningCount(): {$w}" . PHP_EOL;
        echo "errorCount()  : {$e}" . PHP_EOL;
        echo "判定: " . match(true) {
            $e > 0  => "❌ エラーあり({$e} 件)",
            $w > 0  => "⚠️  警告あり({$w} 件)",
            default => "✅ 問題なし",
        } . PHP_EOL;
    }
}

$sources = [
    '正常'     => '<!DOCTYPE html><html><head><title>T</title></head><body><p>OK</p></body></html>',
    '警告あり' => '<html><body><img src="a.jpg"><table><td>x</td></table></body></html>',
    'エラーあり'=> '<html><body><table><td><div>NG</div></td></body></html>',
];

foreach ($sources as $label => $html) {
    echo "--- {$label} ---" . PHP_EOL;
    (new TidyOopWarningChecker($html))->describe();
    echo PHP_EOL;
}

出力例:

--- 正常 ---
warningCount(): 0
errorCount()  : 0
判定: ✅ 問題なし

--- 警告あり ---
warningCount(): 2
errorCount()  : 0
判定: ⚠️  警告あり(2 件)

--- エラーあり ---
warningCount(): 1
errorCount()  : 1
判定: ❌ エラーあり(1 件)

例3: 警告件数にしきい値を設けて合否判定するクラス

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

    public function check(string $label, string $html): bool
    {
        $tidy     = tidy_parse_string($html, [], 'UTF8');
        $warnings = tidy_warning_count($tidy);
        $errors   = tidy_error_count($tidy);

        $pass = ($warnings <= $this->maxWarnings && $errors <= $this->maxErrors);
        $icon = $pass ? '✅ PASS' : '❌ FAIL';

        printf(
            "  %s %-18s warnings=%d/%d errors=%d/%d%s",
            $icon,
            $label,
            $warnings, $this->maxWarnings,
            $errors,   $this->maxErrors,
            PHP_EOL
        );

        return $pass;
    }

    public function checkAll(array $pages): array
    {
        echo "=== しきい値チェック(警告≦{$this->maxWarnings} / エラー≦{$this->maxErrors})===" . PHP_EOL;
        $results = [];
        foreach ($pages as $label => $html) {
            $results[$label] = $this->check($label, $html);
        }
        $passed = count(array_filter($results));
        $total  = count($results);
        echo PHP_EOL . "結果: {$passed}/{$total} 件 PASS" . PHP_EOL;
        return $results;
    }
}

$checker = new HtmlWarningThresholdChecker(maxWarnings: 1, maxErrors: 0);
$checker->checkAll([
    'clean.html'    => '<!DOCTYPE html><html><head><title>T</title></head><body><p>OK</p></body></html>',
    'one-warn.html' => '<html><body><img src="a.jpg"></body></html>',
    'two-warn.html' => '<html><body><img src="a.jpg"><table><td>x</td></table></body></html>',
    'error.html'    => '<html><body><table><td><p>NG</td></table></body></html>',
]);

出力例:

=== しきい値チェック(警告≦1 / エラー≦0)===
  ✅ PASS clean.html        warnings=0/1 errors=0/0
  ✅ PASS one-warn.html     warnings=1/1 errors=0/0
  ❌ FAIL two-warn.html     warnings=2/1 errors=0/0
  ❌ FAIL error.html        warnings=0/1 errors=1/0

結果: 2/4 件 PASS

例4: 複数ページを一括検証して集計レポートを生成するクラス

<?php
class HtmlBatchQualityChecker
{
    /** @var array<string, array{warnings: int, errors: int, access: int, status: int}> */
    private array $results = [];

    public function addAll(array $pages): self
    {
        foreach ($pages as $name => $html) {
            $tidy = tidy_parse_string($html, [], 'UTF8');
            $this->results[$name] = [
                'warnings' => tidy_warning_count($tidy),
                'errors'   => tidy_error_count($tidy),
                'access'   => tidy_access_count($tidy),
                'status'   => tidy_get_status($tidy),
            ];
        }
        return $this;
    }

    public function printReport(): void
    {
        echo "=== 一括品質レポート ===" . PHP_EOL;
        printf("  %-20s %7s %7s %7s %7s%s", "ファイル名", "warn", "error", "access", "status", PHP_EOL);
        echo "  " . str_repeat('-', 52) . PHP_EOL;

        $totals = ['warnings' => 0, 'errors' => 0, 'access' => 0];

        foreach ($this->results as $name => $r) {
            $icon = match($r['status']) { 0 => '✅', 1 => '⚠️ ', 2 => '❌', default => '?' };
            printf(
                "  %s %-18s %7d %7d %7d %7d%s",
                $icon, $name,
                $r['warnings'], $r['errors'], $r['access'], $r['status'],
                PHP_EOL
            );
            $totals['warnings'] += $r['warnings'];
            $totals['errors']   += $r['errors'];
            $totals['access']   += $r['access'];
        }

        echo "  " . str_repeat('-', 52) . PHP_EOL;
        printf(
            "  %-20s %7d %7d %7d%s",
            "合計",
            $totals['warnings'],
            $totals['errors'],
            $totals['access'],
            PHP_EOL
        );
    }
}

$checker = new HtmlBatchQualityChecker();
$checker->addAll([
    'index.html'   => '<!DOCTYPE html><html><head><title>T</title></head><body><p>OK</p></body></html>',
    'about.html'   => '<html><body><img src="a.jpg"><p>About</p></body></html>',
    'product.html' => '<html><body><table><td>A</td><td>B</td></table></body></html>',
    'broken.html'  => '<html><body><table><td><div>NG</div></td></body></html>',
])->printReport();

出力例:

=== 一括品質レポート ===
  ファイル名              warn   error  access  status
  ----------------------------------------------------
  ✅ index.html               0       0       0       0
  ⚠️  about.html               1       0       0       1
  ⚠️  product.html             2       0       0       1
  ❌ broken.html               1       1       0       2
  ----------------------------------------------------
  合計                         4       1       0

例5: 警告件数で品質スコアを算出するクラス

<?php
class HtmlQualityScorer
{
    private int $warnings;
    private int $errors;
    private int $access;

    public function __construct(string $html)
    {
        $tidy          = tidy_parse_string($html, [], 'UTF8');
        $this->warnings = tidy_warning_count($tidy);
        $this->errors   = tidy_error_count($tidy);
        $this->access   = tidy_access_count($tidy);
    }

    public function getScore(): int
    {
        // 100点から警告×3点・エラー×10点・アクセシビリティ×5点を減点
        $score = 100
            - ($this->warnings * 3)
            - ($this->errors   * 10)
            - ($this->access   * 5);
        return max(0, $score);
    }

    public function getGrade(): string
    {
        return match(true) {
            $this->getScore() >= 90 => 'A',
            $this->getScore() >= 70 => 'B',
            $this->getScore() >= 50 => 'C',
            $this->getScore() >= 30 => 'D',
            default                 => 'F',
        };
    }

    public function report(): void
    {
        $score = $this->getScore();
        $grade = $this->getGrade();
        echo "=== 品質スコア ===" . PHP_EOL;
        echo "警告               : {$this->warnings} 件 (-" . ($this->warnings * 3)  . "点)" . PHP_EOL;
        echo "エラー             : {$this->errors}   件 (-" . ($this->errors   * 10) . "点)" . PHP_EOL;
        echo "アクセシビリティ   : {$this->access}   件 (-" . ($this->access   * 5)  . "点)" . PHP_EOL;
        echo "スコア             : {$score} / 100" . PHP_EOL;
        echo "グレード           : {$grade}" . PHP_EOL;
    }
}

$pages = [
    '高品質ページ' => '<!DOCTYPE html><html><head><title>T</title></head><body><p>OK</p></body></html>',
    '軽微な問題'   => '<html><body><img src="a.jpg"><table><td>x</td></table></body></html>',
    '深刻な問題'   => '<html><body><table><td><div>NG</div></td><td><div>NG2</div></td></body></html>',
];

foreach ($pages as $label => $html) {
    echo PHP_EOL . "【{$label}】" . PHP_EOL;
    (new HtmlQualityScorer($html))->report();
}

出力例:

【高品質ページ】
=== 品質スコア ===
警告               : 0 件 (-0点)
エラー             : 0 件 (-0点)
アクセシビリティ   : 0 件 (-0点)
スコア             : 100 / 100
グレード           : A

【軽微な問題】
=== 品質スコア ===
警告               : 2 件 (-6点)
エラー             : 0 件 (-0点)
アクセシビリティ   : 0 件 (-0点)
スコア             : 94 / 100
グレード           : A

【深刻な問題】
=== 品質スコア ===
警告               : 1 件 (-3点)
エラー             : 2 件 (-20点)
アクセシビリティ   : 0 件 (-0点)
スコア             : 77 / 100
グレード           : B

例6: 警告件数の推移をログで記録するクラス

<?php
class HtmlWarningTrendLogger
{
    public function __construct(private readonly string $logFile) {}

    public function log(string $label, string $html): void
    {
        $tidy     = tidy_parse_string($html, [], 'UTF8');
        $warnings = tidy_warning_count($tidy);
        $errors   = tidy_error_count($tidy);
        $status   = tidy_get_status($tidy);

        $statusLabel = match($status) { 0 => 'OK', 1 => 'WARN', 2 => 'ERROR', default => 'UNKNOWN' };

        $line = sprintf(
            "[%s] %-20s | status=%-5s | warnings=%d | errors=%d\n",
            date('Y-m-d H:i:s'),
            $label,
            $statusLabel,
            $warnings,
            $errors
        );

        file_put_contents($this->logFile, $line, FILE_APPEND);
        echo $line;
    }

    public function summary(): void
    {
        if (!file_exists($this->logFile)) {
            echo "ログファイルが存在しません。" . PHP_EOL;
            return;
        }

        $lines       = file($this->logFile, FILE_IGNORE_NEW_LINES) ?: [];
        $totalWarn   = 0;
        $totalError  = 0;

        foreach ($lines as $line) {
            if (preg_match('/warnings=(\d+)/', $line, $m)) {
                $totalWarn += (int) $m[1];
            }
            if (preg_match('/errors=(\d+)/', $line, $m)) {
                $totalError += (int) $m[1];
            }
        }

        echo PHP_EOL . "=== ログサマリー ===" . PHP_EOL;
        echo "記録件数       : " . count($lines) . " 件" . PHP_EOL;
        echo "累計警告件数   : {$totalWarn}"             . PHP_EOL;
        echo "累計エラー件数 : {$totalError}"            . PHP_EOL;
    }
}

$logFile = sys_get_temp_dir() . '/tidy_warning_trend.log';
@unlink($logFile);

$logger = new HtmlWarningTrendLogger($logFile);
$logger->log('page-A', '<!DOCTYPE html><html><head><title>T</title></head><body><p>OK</p></body></html>');
$logger->log('page-B', '<html><body><img src="a.jpg"></body></html>');
$logger->log('page-C', '<html><body><img src="a.jpg"><table><td>x</td></table></body></html>');
$logger->summary();

unlink($logFile);

出力例:

[2026-07-29 12:00:00] page-A               | status=OK    | warnings=0 | errors=0
[2026-07-29 12:00:00] page-B               | status=WARN  | warnings=1 | errors=0
[2026-07-29 12:00:00] page-C               | status=WARN  | warnings=2 | errors=0

=== ログサマリー ===
記録件数       : 3 件
累計警告件数   : 3
累計エラー件数 : 0

例7: 警告・エラー・アクセシビリティを JSON レポートで出力するクラス

<?php
class HtmlQualityJsonReporter
{
    /**
     * @param array<string, string> $pages
     * @return array<string, mixed>
     */
    public function report(array $pages): array
    {
        $report  = [];
        $summary = ['total_warnings' => 0, 'total_errors' => 0, 'total_access' => 0, 'pages' => 0];

        foreach ($pages as $name => $html) {
            $tidy     = tidy_parse_string($html, [], 'UTF8');
            $warnings = tidy_warning_count($tidy);
            $errors   = tidy_error_count($tidy);
            $access   = tidy_access_count($tidy);
            $status   = tidy_get_status($tidy);

            $report[$name] = [
                'status'       => $status,
                'status_label' => match($status) { 0 => 'OK', 1 => 'WARN', 2 => 'ERROR', default => 'UNKNOWN' },
                'warnings'     => $warnings,
                'errors'       => $errors,
                'access'       => $access,
                'error_buffer' => tidy_get_error_buffer($tidy) ?: null,
            ];

            $summary['total_warnings'] += $warnings;
            $summary['total_errors']   += $errors;
            $summary['total_access']   += $access;
            $summary['pages']++;
        }

        return [
            'generated_at' => date('c'),
            'summary'      => $summary,
            'pages'        => $report,
        ];
    }
}

$reporter = new HtmlQualityJsonReporter();
$result   = $reporter->report([
    'index.html'   => '<!DOCTYPE html><html><head><title>T</title></head><body><p>OK</p></body></html>',
    'about.html'   => '<html><body><img src="a.jpg"></body></html>',
    'broken.html'  => '<html><body><table><td><div>NG</div></td></body></html>',
]);

echo json_encode($result, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);

出力例:

{
    "generated_at": "2026-07-29T12:00:00+09:00",
    "summary": {
        "total_warnings": 2,
        "total_errors": 1,
        "total_access": 0,
        "pages": 3
    },
    "pages": {
        "index.html": {
            "status": 0,
            "status_label": "OK",
            "warnings": 0,
            "errors": 0,
            "access": 0,
            "error_buffer": null
        },
        "about.html": {
            "status": 1,
            "status_label": "WARN",
            "warnings": 1,
            "errors": 0,
            "access": 0,
            "error_buffer": "line 1 column 14 - Warning: <img> lacks \"alt\" attribute"
        },
        "broken.html": {
            "status": 2,
            "status_label": "ERROR",
            "warnings": 1,
            "errors": 1,
            "access": 0,
            "error_buffer": "..."
        }
    }
}

7. 関連関数との比較

関数名用途戻り値
tidy_warning_count()警告の件数を取得int
tidy_error_count()エラーの件数を取得int
tidy_access_count()アクセシビリティ警告の件数を取得int
tidy_get_status()問題有無のステータスコード(0/1/2)を取得int
tidy_get_error_buffer()警告・エラーの詳細メッセージを取得string|false
tidy_parse_string()HTML 文字列を解析して tidy オブジェクト生成tidy|false

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

① tidy_clean_repair() 後は件数が変化することがある

tidy_clean_repair() を呼ぶと内部状態が更新され、警告・エラー件数が変わる場合があります。件数の確認は tidy_parse_string() 直後、tidy_clean_repair() の前に行うのが安全です。

$tidy     = tidy_parse_string($html, [], 'UTF8');
$warnings = tidy_warning_count($tidy); // ← ここで取得(clean_repair 前)
tidy_clean_repair($tidy);
// clean_repair 後の件数は変わっている可能性がある

② 警告 0 件でも品質が完全に保証されるわけではない

Tidy が検出しない問題(セマンティクスの誤り・SEO 上の問題・独自仕様のエラー等)は件数に含まれません。あくまで Tidy の構文チェック範囲内の指標として捉えてください。

③ tidy_get_status() との使い分けを意識する

「問題があるかどうかだけ知りたい」なら tidy_get_status() のほうがシンプルです。件数そのものが必要な場合(しきい値チェック・スコア計算等)に tidy_warning_count() を使います。

// 有無だけ判定
if (tidy_get_status($tidy) > 0) { /* 問題あり */ }

// 件数が必要な場合
$count = tidy_warning_count($tidy);
if ($count > 5) { /* 警告が多すぎる */ }

④ アクセシビリティ警告は別カウンタ

tidy_warning_count() にはアクセシビリティ警告は含まれません。アクセシビリティの問題も把握したい場合は tidy_access_count() を別途呼び出してください。


9. まとめ

項目内容
主な用途Tidy が検出した警告の件数を整数で取得する
戻り値int(0 以上の整数、警告なしは 0)
OOP 版$tidy->warningCount()
エラー件数tidy_error_count() で別途取得
アクセシビリティ件数tidy_access_count() で別途取得
詳細メッセージtidy_get_error_buffer() で取得
取得タイミングtidy_clean_repair() の前が安全
よく使う組み合わせtidy_error_count(), tidy_access_count(), tidy_get_status(), tidy_get_error_buffer()

tidy_warning_count() は HTML バリデーションの品質を数値で定量化するための関数です。tidy_get_status() による有無の判定に加え、件数ベースのしきい値チェック・スコア算出・トレンドログ記録といった高度な品質管理の仕組みを構築する際に活用してください。

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