正則處理 HTML 存在天然缺陷:無法正確匹配巢狀結構、屬性值包含 > 時誤判、CDATA/腳本/樣式標簽內容處理困難。雖然 strip_tags() 可用,但無法自定義保留/移除邏輯,且對編碼敏感。
<?php
/**
* HTML 標簽移除器 - 支持巢狀標簽、屬性值保護、實體解碼
*/
class HtmlTagStripper
{
private string $html;
private int $length;
private int $pos = 0;
private int $depth = 0;
private string $result = '';
private bool $inTag = false;
private bool $inAttr = false;
private bool $inScriptOrStyle = false;
private string $tagName = '';
public function __construct(string $html)
{
$this->html = $html;
$this->length = strlen($html);
}
public function strip(bool $decodeEntities = true): string
{
while ($this->pos < $this->length) {
$char = $this->html[$this->pos];
if (!$this->inTag) {
if ($char === '<') {
$this->enterTag();
} elseif (!$this->inScriptOrStyle) {
$this->result .= $char;
}
} else {
$this->processTagChar($char);
}
$this->pos++;
}
$output = $this->result;
if ($decodeEntities) {
$output = html_entity_decode($output, ENT_QUOTES | ENT_HTML5, 'UTF-8');
}
return trim(preg_replace('/\s+/', ' ', $output));
}
private function enterTag(): void
{
$this->inTag = true;
$this->tagName = '';
// 預讀標簽名
$nextPos = $this->pos + 1;
if ($nextPos < $this->length && $this->html[$nextPos] === '/') {
$nextPos++;
}
$start = $nextPos;
while ($nextPos < $this->length) {
$c = $this->html[$nextPos];
if (ctype_space($c) || $c === '>' || $c === '/') break;
$nextPos++;
}
$this->tagName = strtolower(substr($this->html, $start, $nextPos - $start));
$this->inScriptOrStyle = in_array($this->tagName, ['script', 'style', 'noscript']);
}
private function processTagChar(string $char): void
{
if ($char === '"' || $char === "'") {
$this->inAttr = !$this->inAttr;
return;
}
if (!$this->inAttr && $char === '>') {
$this->exitTag();
}
}
private function exitTag(): void
{
$this->inTag = false;
$this->inAttr = false;
if (in_array($this->tagName, ['script', 'style', 'noscript'])) {
$this->inScriptOrStyle = false;
}
}
}
// 使用示例
$html = '<div class="box"><p>Hello <span>World</span></p><script>alert(1)</script></div>';
$stripper = new HtmlTagStripper($html);
echo $stripper->strip(); // 輸出: Hello World
| 方案 | 巢狀支持 | 屬性值保護 | 腳本/樣式過濾 | 實體解碼 | 性能 |
|---|---|---|---|---|---|
| strip_tags() | ✓ | ✗ | ✗ | ✗ | ★★★★★ |
| 正則替換 | ✗ | ✗ | ✗ | ✗ | ★★★☆☆ |
| DOMDocument | ✓ | ✓ | ✓ | ✓ | ★★☆☆☆ |
| 本文狀態機 | ✓ | ✓ | ✓ | 可選 | ★★★★☆ |