[PHP]tidy_parse_string完全解説|HTML文字列をTidyで解析・修復・整形する方法

PHP

1. 関数概要

tidy_parse_string は、PHP の Tidy 拡張が提供する HTML/XHTML/XML 文字列を直接受け取って解析する関数です。動的に生成した HTML・データベースから取得した HTML・API レスポンスのマークアップなど、ファイルを介さずにメモリ上の文字列を即座に Tidy で処理できます。Tidy を使う上で最も頻繁に呼び出す中心的な関数です。

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

2. 構文

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

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

パラメータ

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

戻り値

呼び出し方成功失敗
手続き型tidy オブジェクトfalse
OOP 型truefalse

3. 主なオプション一覧

$config に渡せる代表的な Tidy オプションです。

オプション名デフォルト説明
indentboolfalseインデントを付ける
indent-spacesint2インデントのスペース数
wrapint68折り返し幅(文字数)。0 で折り返しなし
output-xhtmlboolfalseXHTML 形式で出力する
output-xmlboolfalseXML 形式で出力する
doctypestring'auto'DOCTYPE の種類('html5', 'strict', 'loose', 'omit' 等)
char-encodingstring'ascii'入出力文字エンコーディング
drop-empty-elementsbooltrue空要素を削除する
cleanboolfalse冗長なスタイルを整理する
show-warningsbooltrue警告を表示する
quietboolfalse情報メッセージを抑制する

4. 動作概念図

┌──────────────────────────────────────────────────────────┐
│  入力: HTML 文字列(メモリ上)                            │
│  "<html><body><P>未閉じ<br></body>"                       │
└─────────────────────┬────────────────────────────────────┘
                      │ tidy_parse_string($string, $config, $encoding)
                      ▼
┌──────────────────────────────────────────────────────────┐
│  Tidy 解析エンジン                                        │
│  ・エンコード変換     ・DOCTYPE 検出                      │
│  ・構文エラー収集     ・内部ツリー構築                    │
└─────────────────────┬────────────────────────────────────┘
                      │
          ┌───────────┴───────────┐
      成功 │                       │ 失敗
          ▼                       ▼
   tidy オブジェクト            false
          │
          ▼ tidy_clean_repair()
   修復・整形済み tidy
          │
          ▼ tidy_get_output()
   整形済み HTML 文字列(string)

5. 基本的な使い方

<?php
$html = '<html><body><P>未閉じタグ<br>テキスト</body>';

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

if ($tidy !== false) {
    tidy_clean_repair($tidy);
    echo tidy_get_output($tidy);
}

出力例:

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

6. 実践的なコード例

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

<?php
class HtmlTidyFormatter
{
    private array $config;

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

    public function format(string $html): string|false
    {
        $tidy = tidy_parse_string($html, $this->config, 'UTF8');
        if ($tidy === false) {
            return false;
        }
        tidy_clean_repair($tidy);
        return tidy_get_output($tidy);
    }
}

$formatter = new HtmlTidyFormatter();
$dirty = '<HTML><BODY><H1>タイトル<P>本文<br>テキスト</P></BODY>';
$clean = $formatter->format($dirty);
echo $clean ?? '整形失敗';

出力例:

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

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

<?php
class TidyOopStringParser
{
    private tidy $tidy;

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

    public function parse(string $html, array $config = [], string $encoding = 'UTF8'): bool
    {
        $result = $this->tidy->parseString($html, $config, $encoding);
        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(); }
    public function isXml(): bool        { return $this->tidy->isXml(); }
}

$parser = new TidyOopStringParser();

$html = '<!DOCTYPE html><html><head><title>OOP テスト</title></head><body><p>本文</p></body></html>';

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

出力例:

ステータス: 0
XHTML:      false
<!DOCTYPE html>
<html>
<head>
  <title>OOP テスト</title>
</head>
<body>
  <p>本文</p>
</body>
</html>

例3: バリデーションと整形を分離するクラス

<?php
class HtmlValidator
{
    private tidy   $tidy;
    private int    $status;
    private string $errorBuffer;

    public function validate(string $html): self
    {
        $this->tidy        = tidy_parse_string($html, [], 'UTF8');
        $this->status      = tidy_get_status($this->tidy);
        $this->errorBuffer = tidy_get_error_buffer($this->tidy) ?: '';
        return $this;
    }

    public function isValid(): bool    { return $this->status === 0; }
    public function hasWarning(): bool { return $this->status === 1; }
    public function hasError(): bool   { return $this->status === 2; }

    public function repair(array $config = ['indent' => true]): string
    {
        $tidy = tidy_parse_string(
            tidy_get_output($this->tidy),   // 解析済み文字列を再利用
            $config,
            'UTF8'
        );
        tidy_clean_repair($tidy);
        return tidy_get_output($tidy);
    }

    public function report(): void
    {
        $label = match($this->status) {
            0 => '✅ 正常', 1 => '⚠️  警告あり', 2 => '❌ エラーあり', default => '不明',
        };
        echo "ステータス: {$label}" . PHP_EOL;
        if ($this->errorBuffer !== '') {
            echo "詳細:" . PHP_EOL . $this->errorBuffer . PHP_EOL;
        }
    }
}

$validator = new HtmlValidator();
$validator->validate('<html><body><p>未閉じ<b>太字</body></html>');
$validator->report();

echo PHP_EOL . "=== 修復結果 ===" . PHP_EOL;
echo $validator->repair();

出力例:

ステータス: ⚠️  警告あり
詳細:
line 1 column 26 - Warning: missing </b> before </body>

=== 修復結果 ===
<!DOCTYPE html>
<html>
<head>
  <title></title>
</head>
<body>
  <p>未閉じ<b>太字</b></p>
</body>
</html>

例4: 複数の HTML を一括処理して結果を配列で返すクラス

<?php
class BulkHtmlProcessor
{
    private array $config;

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

    /**
     * @param array<string, string> $pages
     * @return array<string, array{status: int, output: string, error: string}>
     */
    public function processAll(array $pages): array
    {
        $results = [];
        foreach ($pages as $name => $html) {
            $tidy   = tidy_parse_string($html, $this->config, 'UTF8');
            if ($tidy === false) {
                $results[$name] = ['status' => -1, 'output' => '', 'error' => 'parse failed'];
                continue;
            }
            tidy_clean_repair($tidy);
            $results[$name] = [
                'status' => tidy_get_status($tidy),
                'output' => tidy_get_output($tidy),
                'error'  => tidy_get_error_buffer($tidy) ?: '',
            ];
        }
        return $results;
    }

    public function printSummary(array $results): void
    {
        echo "=== 一括処理サマリー ===" . PHP_EOL;
        foreach ($results as $name => $r) {
            $icon = match($r['status']) { 0 => '✅', 1 => '⚠️ ', 2 => '❌', default => '?' };
            printf("  %s %-15s status=%d  %d bytes%s",
                $icon, $name, $r['status'], strlen($r['output']), PHP_EOL);
        }
    }
}

$processor = new BulkHtmlProcessor();
$pages     = [
    'index'   => '<!DOCTYPE html><html><head><title>T</title></head><body><p>OK</p></body></html>',
    'about'   => '<html><body><p>警告あり<br></body></html>',
    'contact' => '<HTML><BODY><TABLE><TD>セル</TD></TABLE></BODY></HTML>',
];

$results = $processor->processAll($pages);
$processor->printSummary($results);

出力例:

=== 一括処理サマリー ===
  ✅ index           status=0  183 bytes
  ⚠️  about           status=1  196 bytes
  ✅ contact          status=0  207 bytes

例5: 動的生成 HTML をサニタイズして安全に出力するクラス

<?php
class SafeHtmlSanitizer
{
    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
    {
        // ユーザー入力を一度エスケープしてから tidy に通す
        $html = '<div>' . $userInput . '</div>';
        $tidy = tidy_parse_string($html, $this->config, 'UTF8');

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

        tidy_clean_repair($tidy);
        $output = tidy_get_output($tidy);

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

        return $output;
    }
}

$sanitizer = new SafeHtmlSanitizer();
$userInput = '<p onclick="alert(1)">テキスト</p><script>evil()</script><b>太字</b>';
echo $sanitizer->sanitize($userInput);

出力例:

<div>
  <p>テキスト</p>
  <b>太字</b>
</div>

例6: エンコーディングを変換しながら整形するクラス

<?php
class EncodingAwareFormatter
{
    /**
     * @param string $html      入力 HTML 文字列
     * @param string $inputEnc  Tidy エンコーディング名(例: 'latin1', 'UTF8', 'shiftjis')
     * @param array  $config    追加オプション
     */
    public function format(string $html, string $inputEnc = 'UTF8', array $config = []): string
    {
        $mergedConfig = array_merge(
            ['indent' => true, 'wrap' => 80, 'char-encoding' => strtolower($inputEnc)],
            $config
        );

        $tidy = tidy_parse_string($html, $mergedConfig, $inputEnc);
        if ($tidy === false) {
            return $html; // フォールバック: 元の文字列を返す
        }

        tidy_clean_repair($tidy);

        // 現在のエンコーディング設定を確認
        $enc = tidy_getopt($tidy, 'char-encoding');
        echo "使用エンコーディング: {$enc}" . PHP_EOL;

        return tidy_get_output($tidy);
    }
}

$formatter = new EncodingAwareFormatter();
$html      = '<html><body><p>UTF-8 テキスト:日本語サンプル</p></body></html>';
echo $formatter->format($html, 'UTF8');

出力例:

使用エンコーディング: utf8
<!DOCTYPE html>
<html>
<head>
  <title></title>
</head>
<body>
  <p>UTF-8 テキスト:日本語サンプル</p>
</body>
</html>

例7: 解析結果を統合レポートとして返すクラス

<?php
readonly class TidyStringReport
{
    public function __construct(
        public string $output,
        public int    $status,
        public string $statusLabel,
        public bool   $isXhtml,
        public bool   $isXml,
        public int    $htmlVersion,
        public string $errorBuffer,
        public string $release,
        public int    $outputBytes,
    ) {}
}

class TidyStringAnalyzer
{
    public function analyze(string $html, array $config = []): TidyStringReport
    {
        $merged = array_merge(['indent' => true, 'wrap' => 80], $config);
        $tidy   = tidy_parse_string($html, $merged, 'UTF8');

        if ($tidy === false) {
            throw new \RuntimeException('tidy_parse_string() が失敗しました。');
        }

        tidy_clean_repair($tidy);
        $status = tidy_get_status($tidy);
        $output = tidy_get_output($tidy);

        return new TidyStringReport(
            output:      $output,
            status:      $status,
            statusLabel: match($status) { 0 => 'OK', 1 => 'WARN', 2 => 'ERROR', default => 'UNKNOWN' },
            isXhtml:     tidy_is_xhtml($tidy),
            isXml:       tidy_is_xml($tidy),
            htmlVersion: tidy_get_html_ver($tidy),
            errorBuffer: tidy_get_error_buffer($tidy) ?: '',
            release:     tidy_get_release(),
            outputBytes: strlen($output),
        );
    }

    public function printReport(TidyStringReport $r): void
    {
        echo "=== Tidy 解析レポート ===" . PHP_EOL;
        echo "ステータス     : {$r->status} ({$r->statusLabel})" . PHP_EOL;
        echo "isXhtml()      : " . var_export($r->isXhtml, true)  . PHP_EOL;
        echo "isXml()        : " . var_export($r->isXml,   true)  . PHP_EOL;
        echo "HTML バージョン: " . ($r->htmlVersion ?: 'HTML5/不明') . PHP_EOL;
        echo "出力バイト数   : {$r->outputBytes} bytes"            . PHP_EOL;
        echo "libtidy        : {$r->release}"                      . PHP_EOL;
        echo "エラーバッファ : " . ($r->errorBuffer ?: '(なし)') . PHP_EOL;
        echo PHP_EOL . "--- 整形済み出力 ---" . PHP_EOL;
        echo $r->output;
    }
}

$analyzer = new TidyStringAnalyzer();
$report   = $analyzer->analyze(
    '<html><body><p>統合テスト<br></p></body></html>',
    ['output-xhtml' => false]
);
$analyzer->printReport($report);

出力例:

=== Tidy 解析レポート ===
ステータス     : 0 (OK)
isXhtml()      : false
isXml()        : false
HTML バージョン: HTML5/不明
出力バイト数   : 196 bytes
libtidy        : 1st August 2023
エラーバッファ : (なし)

--- 整形済み出力 ---
<!DOCTYPE html>
<html>
<head>
  <title></title>
</head>
<body>
  <p>統合テスト<br></p>
</body>
</html>

7. 関連関数との比較

関数名入力用途戻り値
tidy_parse_string()HTML 文字列動的生成 HTML・API レスポンス等の解析tidy|false
tidy_parse_file()ファイルパス / URLファイルを直接読み込んで解析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. よくある落とし穴と注意点

① エンコーディングを指定しないと日本語が文字化けする

$encoding を省略するとデフォルトの ascii が使われ、マルチバイト文字が壊れることがあります。日本語を扱う場合は必ず 'UTF8' を指定してください。

// NG: エンコーディング未指定 → 日本語が文字化けする恐れ
$tidy = tidy_parse_string($html, ['indent' => true]);

// OK: UTF8 を明示
$tidy = tidy_parse_string($html, ['indent' => true], 'UTF8');

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

tidy_parse_string() は解析のみを行い、修復は行いません。tidy_get_output() の前に必ず tidy_clean_repair() を呼び出してください。

// NG: clean_repair なしで出力(未修復)
$tidy = tidy_parse_string($html, [], 'UTF8');
echo tidy_get_output($tidy); // 修復されていない可能性がある

// OK: clean_repair 後に出力
$tidy = tidy_parse_string($html, [], 'UTF8');
tidy_clean_repair($tidy);
echo tidy_get_output($tidy);

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

手続き型は tidy|false を返しますが、OOP 版 $tidy->parseString() の戻り値は bool です。解析結果は $tidy インスタンス自体に格納されます。

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

// OOP 型: 戻り値は bool、$tidy 自身が結果を保持
$tidy = new tidy();
$ok   = $tidy->parseString($html, [], 'UTF8'); // bool
if ($ok) { $tidy->cleanRepair(); }

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

PHP の mb_ 系関数とは異なる表記です。'UTF-8' ではなく 'UTF8''ISO-8859-1' ではなく 'latin1' のように指定します。

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

⑤ $config に空配列と null の違い

$config = [](空配列)は「オプションなし=すべてデフォルト値」を意味し、$config = null も同様です。ただし明示的に [] を渡す書き方のほうがコードの意図が明確になります。


9. まとめ

項目内容
主な用途HTML/XHTML/XML 文字列を Tidy で解析する
戻り値(手続き型)tidy オブジェクト(成功)/ false(失敗)
戻り値(OOP 型)bool(成功/失敗)
tidy_parse_file() との違い文字列を受け取る・URL 非対応・include_path 非対応
エンコーディング'UTF8', 'latin1' など Tidy 固有の表記を使う
必須の後続処理tidy_clean_repair()tidy_get_output()
基本3ステップtidy_parse_string()tidy_clean_repair()tidy_get_output()

tidy_parse_string() は Tidy を使う際に最初に呼び出す最重要関数です。tidy_parse_string()tidy_clean_repair()tidy_get_output() の3ステップを基本パターンとして確実に習得し、バリデーション・一括処理・統合レポート生成など多様な用途に応用してください。

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