1. 関数概要
tidy_repair_string は、PHP の Tidy 拡張が提供する HTML/XHTML 文字列の解析・修復・整形をワンステップで行い、結果を文字列で返す関数です。tidy_parse_string() → tidy_clean_repair() → tidy_get_output() の3ステップを1回の呼び出しにまとめたショートカット関数であり、tidy オブジェクトを生成せずに整形済み文字列を即座に得られます。
| 項目 | 内容 |
|---|---|
| 関数名 | tidy_repair_string |
| 所属拡張 | Tidy |
| 戻り値の型 | string|false |
| 手続き型 / OOP | 手続き型のみ(OOP 版なし) |
| PHP バージョン | PHP 5 以降 |
| 公式ドキュメント | https://www.php.net/manual/ja/tidy.repairstring.php |
2. 構文
tidy_repair_string(
string $string,
array|string|null $config = null,
?string $encoding = null
): string|false
パラメータ
| パラメータ | 型 | 説明 |
|---|---|---|
$string | string | 修復する HTML/XHTML/XML 文字列 |
$config | array|string|null | Tidy オプションの連想配列、または tidyrc 設定ファイルのパス。null でデフォルト設定 |
$encoding | string|null | 入出力エンコーディング(例: 'UTF8', 'latin1')。null でデフォルト(ascii) |
戻り値
| 結果 | 戻り値 |
|---|---|
| 成功 | 修復・整形済みの HTML 文字列(string) |
| 失敗 | false |
3. tidy_repair_string と関連関数の比較
| 観点 | tidy_repair_string() | tidy_repair_file() | tidy_parse_string() |
|---|---|---|---|
| 入力 | HTML 文字列 | ファイルパス / URL | HTML 文字列 |
| 戻り値 | string|false | string|false | tidy|false |
| tidy オブジェクト | 不要・生成されない | 不要・生成されない | 生成される |
| ステータス取得 | ❌ 不可 | ❌ 不可 | ✅ 可 |
| エラーバッファ取得 | ❌ 不可 | ❌ 不可 | ✅ 可 |
| DOM ツリー走査 | ❌ 不可 | ❌ 不可 | ✅ 可 |
| 向いている用途 | 文字列の簡易整形 | ファイルの簡易整形 | 詳細な検査・分析 |
4. 動作概念図
┌──────────────────────────────────────────────────────────┐
│ 入力: HTML 文字列(メモリ上) │
│ "<html><body><P>未閉じ<br></body>" │
└─────────────────────┬────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────┐
│ tidy_repair_string($string, $config, $encoding) │
│ │
│ 内部で自動実行: │
│ ① tidy_parse_string() 文字列の解析 │
│ ② tidy_clean_repair() 修復・整形 │
│ ③ tidy_get_output() 文字列化 │
└─────────────────────┬────────────────────────────────────┘
│
┌───────────┴───────────┐
成功 │ │ 失敗
▼ ▼
整形済み HTML 文字列 false
(string)
5. 基本的な使い方
<?php
$dirty = '<HTML><BODY><P>未閉じタグ<br>テキスト<b>太字</BODY>';
$clean = tidy_repair_string($dirty, ['indent' => true, 'wrap' => 80], 'UTF8');
if ($clean !== false) {
echo $clean;
} else {
echo "修復に失敗しました。" . PHP_EOL;
}
出力例:
<!DOCTYPE html>
<html>
<head>
<title></title>
</head>
<body>
<p>未閉じタグ<br>
テキスト<b>太字</b></p>
</body>
</html>
6. 実践的なコード例
例1: HTML を修復して返すシンプルなクラス
<?php
class HtmlRepairService
{
public function __construct(
private readonly array $config = ['indent' => true, 'wrap' => 80],
private readonly string $encoding = 'UTF8'
) {}
public function repair(string $html): string
{
$result = tidy_repair_string($html, $this->config, $this->encoding);
return $result !== false ? $result : $html; // 失敗時はそのまま返す
}
public function repairOrFail(string $html): string
{
$result = tidy_repair_string($html, $this->config, $this->encoding);
if ($result === false) {
throw new \RuntimeException('tidy_repair_string() による修復に失敗しました。');
}
return $result;
}
}
$service = new HtmlRepairService();
$dirty = '<HTML><BODY><H1>タイトル<P>本文テキスト</BODY>';
echo $service->repair($dirty);
出力例:
<!DOCTYPE html>
<html>
<head>
<title></title>
</head>
<body>
<h1>タイトル</h1>
<p>本文テキスト</p>
</body>
</html>
例2: 複数の HTML を一括修復するクラス
<?php
class BulkHtmlRepairer
{
private int $successCount = 0;
private int $failCount = 0;
public function __construct(
private readonly array $config = ['indent' => true, 'wrap' => 80]
) {}
/**
* @param array<string, string> $pages
* @return array<string, string|false>
*/
public function repairAll(array $pages): array
{
$results = [];
foreach ($pages as $name => $html) {
$result = tidy_repair_string($html, $this->config, 'UTF8');
$results[$name] = $result;
$result !== false ? $this->successCount++ : $this->failCount++;
}
return $results;
}
public function printSummary(array $results): void
{
echo "=== 一括修復サマリー ===" . PHP_EOL;
foreach ($results as $name => $output) {
$icon = $output !== false ? '✅' : '❌';
$bytes = $output !== false ? strlen($output) . ' bytes' : '失敗';
printf(" %s %-15s → %s%s", $icon, $name, $bytes, PHP_EOL);
}
echo PHP_EOL . "成功: {$this->successCount} / 失敗: {$this->failCount}" . PHP_EOL;
}
}
$repairer = new BulkHtmlRepairer();
$pages = [
'index' => '<html><body><h1>トップ<p>本文</body>',
'about' => '<HTML><BODY><H2>概要</H2><P>テキスト</BODY>',
'contact' => '<html><body><form><input type=text name=email></form></body></html>',
'news' => '<!DOCTYPE html><html><head><title>News</title></head><body><p>最新情報</p></body></html>',
];
$results = $repairer->repairAll($pages);
$repairer->printSummary($results);
echo PHP_EOL . "=== index の修復結果 ===" . PHP_EOL;
echo $results['index'];
出力例:
=== 一括修復サマリー ===
✅ index → 195 bytes
✅ about → 198 bytes
✅ contact → 226 bytes
✅ news → 183 bytes
成功: 4 / 失敗: 0
=== index の修復結果 ===
<!DOCTYPE html>
<html>
<head>
<title></title>
</head>
<body>
<h1>トップ</h1>
<p>本文</p>
</body>
</html>
例3: 修復前後を比較して差分を表示するクラス
<?php
class HtmlRepairDiffViewer
{
public function compare(string $original, array $config = ['indent' => true]): void
{
$repaired = tidy_repair_string($original, $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(" %d bytes / %d 行%s", strlen($original), $origLines, PHP_EOL);
echo $original . PHP_EOL . PHP_EOL;
echo "=== 修復後 ===" . PHP_EOL;
printf(" %d bytes / %d 行 (差分: %+d bytes)%s",
strlen($repaired),
$repairedLines,
strlen($repaired) - strlen($original),
PHP_EOL
);
echo $repaired;
}
}
$viewer = new HtmlRepairDiffViewer();
$viewer->compare('<html><body><p>未閉じ<b>太字<br>テキスト</body>');
出力例:
=== 修復前 ===
46 bytes / 1 行
<html><body><p>未閉じ<b>太字<br>テキスト</body>
=== 修復後 ===
220 bytes / 14 行 (差分: +174 bytes)
<!DOCTYPE html>
<html>
<head>
<title></title>
</head>
<body>
<p>未閉じ<b>太字<br>
テキスト</b></p>
</body>
</html>
例4: XHTML 形式で修復・出力するクラス
<?php
class XhtmlRepairConverter
{
private readonly array $config;
public function __construct(int $wrapWidth = 80)
{
$this->config = [
'output-xhtml' => true,
'indent' => true,
'wrap' => $wrapWidth,
'char-encoding' => 'utf8',
];
}
public function convert(string $html): string|false
{
return tidy_repair_string($html, $this->config, 'UTF8');
}
public function isXhtmlOutput(string $output): bool
{
return str_contains($output, 'xmlns="http://www.w3.org/1999/xhtml"');
}
}
$converter = new XhtmlRepairConverter();
$html = '<html><body><br><img src="photo.jpg"><p>テキスト</body></html>';
$xhtml = $converter->convert($html);
if ($xhtml !== false) {
echo "XHTML 変換確認: " . ($converter->isXhtmlOutput($xhtml) ? '✅ OK' : '❌ NG') . PHP_EOL;
echo $xhtml;
}
出力例:
XHTML 変換確認: ✅ OK
<?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="" />
<p>テキスト</p>
</body>
</html>
例5: ユーザー入力を安全に整形するサニタイザークラス
<?php
class UserInputHtmlSanitizer
{
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
{
// インラインコンテンツとして div でラップ
$wrapped = '<div>' . $userInput . '</div>';
$repaired = tidy_repair_string($wrapped, $this->config, 'UTF8');
if ($repaired === false) {
return htmlspecialchars($userInput, ENT_QUOTES, 'UTF-8');
}
// body 内のみ抽出
if (preg_match('/<body[^>]*>(.*?)<\/body>/si', $repaired, $m)) {
return trim($m[1]);
}
return $repaired;
}
}
$sanitizer = new UserInputHtmlSanitizer();
$inputs = [
'<p onclick="alert(1)">テキスト</p><script>evil()</script><b>太字</b>',
'<h2>見出し</h2><ul><li>項目1</li><li>項目2</ul>',
'プレーンテキスト(タグなし)',
];
foreach ($inputs as $i => $input) {
echo "=== 入力 " . ($i + 1) . " ===" . PHP_EOL;
echo $sanitizer->sanitize($input) . PHP_EOL . PHP_EOL;
}
出力例:
=== 入力 1 ===
<div>
<p>テキスト</p>
<b>太字</b>
</div>
=== 入力 2 ===
<div>
<h2>見出し</h2>
<ul>
<li>項目1</li>
<li>項目2</li>
</ul>
</div>
=== 入力 3 ===
<div>
プレーンテキスト(タグなし)
</div>
例6: tidyrc 設定ファイルを使って修復するクラス
<?php
class TidyRcStringRepairer
{
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));
}
public function repair(string $html, string $encoding = 'UTF8'): string|false
{
// $config に設定ファイルパス(string)を渡す
return tidy_repair_string($html, $this->rcPath, $encoding);
}
public function __destruct()
{
if (file_exists($this->rcPath)) {
unlink($this->rcPath);
}
}
}
$repairer = new TidyRcStringRepairer([
'indent' => true,
'wrap' => 100,
'output-xhtml' => false,
'quiet' => true,
]);
$html = '<html><body><p>tidyrc 経由のテスト</p></body></html>';
$result = $repairer->repair($html);
echo $result !== false ? $result : "❌ 修復失敗" . PHP_EOL;
出力例:
<!DOCTYPE html>
<html>
<head>
<title></title>
</head>
<body>
<p>tidyrc 経由のテスト</p>
</body>
</html>
例7: 修復結果の統計を収集するレポータークラス
<?php
class HtmlRepairStatReporter
{
/** @var array<string, array{original: int, repaired: int|null, success: bool}> */
private array $stats = [];
public function __construct(
private readonly array $config = ['indent' => true, 'wrap' => 80]
) {}
public function add(string $label, string $html): self
{
$repaired = tidy_repair_string($html, $this->config, 'UTF8');
$this->stats[$label] = [
'original' => strlen($html),
'repaired' => $repaired !== false ? strlen($repaired) : null,
'success' => $repaired !== false,
];
return $this;
}
public function printReport(): void
{
echo "=== 修復統計レポート ===" . PHP_EOL;
printf(" %-18s %8s %8s %8s%s", "ラベル", "修復前", "修復後", "差分", PHP_EOL);
echo " " . str_repeat('-', 46) . PHP_EOL;
$totalOrig = 0;
$totalRepaired = 0;
$successCount = 0;
foreach ($this->stats as $label => $s) {
$icon = $s['success'] ? '✅' : '❌';
$repStr = $s['repaired'] !== null ? $s['repaired'] . 'B' : '失敗';
$diffStr = $s['repaired'] !== null
? sprintf('%+d', $s['repaired'] - $s['original']) . 'B'
: '-';
printf(" %s %-15s %8s %8s %8s%s",
$icon, $label,
$s['original'] . 'B',
$repStr,
$diffStr,
PHP_EOL
);
$totalOrig += $s['original'];
if ($s['repaired'] !== null) {
$totalRepaired += $s['repaired'];
$successCount++;
}
}
echo " " . str_repeat('-', 46) . PHP_EOL;
printf(" %-18s %8s %8s %8s%s",
"合計 ({$successCount}件成功)",
$totalOrig . 'B',
$totalRepaired . 'B',
sprintf('%+d', $totalRepaired - $totalOrig) . 'B',
PHP_EOL
);
}
public function toJson(): string
{
return json_encode(
['stats' => $this->stats, 'generated_at' => date('c')],
JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE
);
}
}
$reporter = new HtmlRepairStatReporter();
$reporter
->add('正常な HTML', '<!DOCTYPE html><html><head><title>T</title></head><body><p>OK</p></body></html>')
->add('大文字タグ', '<HTML><BODY><H1>見出し</H1><P>本文</P></BODY></HTML>')
->add('未閉じタグ', '<html><body><p>段落<b>太字<br>テキスト</body>')
->add('属性なしタグ', '<html><body><img src="a.jpg"><input type=text></body></html>');
$reporter->printReport();
出力例:
=== 修復統計レポート ===
ラベル 修復前 修復後 差分
----------------------------------------------
✅ 正常な HTML 87B 183B +96B
✅ 大文字タグ 54B 195B +141B
✅ 未閉じタグ 46B 220B +174B
✅ 属性なしタグ 65B 234B +169B
----------------------------------------------
合計 (4件成功) 252B 832B +580B
7. 関連関数との比較
| 関数名 | 入力 | 戻り値 | tidy オブジェクト |
|---|---|---|---|
tidy_repair_string() | HTML 文字列 | string|false | 不要・生成されない |
tidy_repair_file() | ファイルパス / URL | string|false | 不要・生成されない |
tidy_parse_string() | HTML 文字列 | tidy|false | 生成される |
tidy_parse_file() | ファイルパス / URL | tidy|false | 生成される |
tidy_clean_repair() | tidy オブジェクト | bool | 必要 |
tidy_get_output() | tidy オブジェクト | string | 必要 |
使い分けのポイント: 「修復済みの文字列だけ欲しい」なら
tidy_repair_string()、「ステータス・エラーバッファ・DOM ツリーも検査したい」ならtidy_parse_string()+ 3ステップ処理を選んでください。
8. よくある落とし穴と注意点
① ステータスやエラーバッファは取得できない
tidy_repair_string() は tidy オブジェクトを返さないため、tidy_get_status() や tidy_get_error_buffer() を呼び出せません。警告・エラーの詳細が必要な場合は tidy_parse_string() を使ってください。
// NG: tidy オブジェクトが存在しない
$result = tidy_repair_string($html, [], 'UTF8');
tidy_get_status($result); // $result は string|false なので使えない
// OK: 詳細が必要なら tidy_parse_string() を使う
$tidy = tidy_parse_string($html, [], 'UTF8');
tidy_clean_repair($tidy);
$status = tidy_get_status($tidy);
$output = tidy_get_output($tidy);
② OOP 版は存在しない
tidy_repair_string() には OOP 版($tidy->repairString() 相当)がありません。OOP スタイルで同等の処理を行う場合は $tidy->parseString() + $tidy->cleanRepair() + $tidy->value を使ってください。
// OOP スタイルで同等の処理
$tidy = new tidy();
$tidy->parseString($html, ['indent' => true], 'UTF8');
$tidy->cleanRepair();
$repaired = $tidy->value;
③ エンコーディング名は Tidy 固有の表記を使う
'UTF-8' ではなく 'UTF8'、'ISO-8859-1' ではなく 'latin1' のように Tidy 固有の表記が必要です。日本語を扱う場合は必ず 'UTF8' を指定してください。
tidy_repair_string($html, [], 'UTF8'); // ✅ 正しい
tidy_repair_string($html, [], 'UTF-8'); // ⚠️ 認識されない場合がある
④ false 返却時のフォールバックを用意する
tidy_repair_string() が false を返した場合に何もしないと、後続の処理で予期しないエラーが発生します。フォールバックを明示的に実装してください。
// NG: false を文字列として使う恐れ
$output = tidy_repair_string($html, [], 'UTF8');
echo $output; // false の場合に何も表示されないか警告
// OK: 必ず確認する
$output = tidy_repair_string($html, [], 'UTF8');
if ($output === false) {
$output = htmlspecialchars($html, ENT_QUOTES, 'UTF-8'); // フォールバック
}
echo $output;
⑤ $config は配列か設定ファイルパスのどちらかを渡す
オプションは ['indent' => true] のような連想配列か、tidyrc ファイルのパス文字列を渡します。両方を同時には渡せません。
// 配列でオプション指定
tidy_repair_string($html, ['indent' => true, 'wrap' => 80], 'UTF8');
// tidyrc ファイルのパスで指定
tidy_repair_string($html, '/path/to/.tidyrc', 'UTF8');
9. まとめ
| 項目 | 内容 |
|---|---|
| 主な用途 | HTML 文字列をワンステップで修復・整形して文字列を得る |
| 戻り値 | string(修復済み HTML)/ false(失敗) |
| OOP 版 | なし |
| tidy オブジェクト | 生成されない(ステータス・エラー取得不可) |
| エンコーディング | 'UTF8', 'latin1' など Tidy 固有の表記を使う |
| 詳細な検査が必要なとき | tidy_parse_string() の3ステップ処理に切り替える |
tidy_repair_file() との違い | 入力がファイルパスではなく文字列 |
tidy_repair_string() は Tidy ファミリーの中で最もシンプルに使える関数です。tidy オブジェクトを介さず1行で整形済み文字列を得られるため、動的生成 HTML の簡易整形・一括処理・ユーザー入力のサニタイズなど、コードを簡潔に保ちたい場面に最適です。エラーの詳細や DOM 走査が必要になったタイミングで tidy_parse_string() ベースの処理に移行するのが実践的な判断基準です。
