首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 網頁程式設計 >> PHP 程式設計 >> PHP 綜合知識

PHP 關鍵字替換控制:單次替換、限制次數與避開 HTML 屬性的實戰策略

     編輯:PHP 綜合知識

內容管理系統常需對文章進行關鍵字連結、敏感詞過濾或廣告植入,但必須每個關鍵字僅替換第一次出現、不破壞 HTML 標籤屬性(如 alt、title)、支援大小寫不敏感。本文提供三種方案:preg_replace_callback 計數器、strpos+substr_replace 精確定位、以及基於 DOMDocument 的安全替換。

一、需求場景與陷阱

<?php
$content = '<img src="apple.jpg" alt="Apple iPhone"> Apple 發布新款 Apple Watch';
// 目標:將 "Apple" 替換為 <a href="/tag/apple">Apple</a>
// 錯誤結果:alt 屬性也被替換、所有 Apple 都被替換
// 正確結果:僅替換第一個純文本 "Apple",保留 alt 屬性
?>

二、方案一:preg_replace_callback + 靜態計數器(簡潔、效能佳)

<?php
function replaceFirstOccurrence(string $subject, string $search, string $replace, bool $caseInsensitive = true): string {
    $flags = $caseInsensitive ? 'i' : '';
    $pattern = '/'.preg_quote($search, '/').'/'.$flags;
    $count = 0;
    return preg_replace_callback($pattern, function($m) use (&$count, $replace) {
        return ++$count === 1 ? $replace : $m[0];
    }, $subject);
}

// 批次處理多個關鍵字(各自限制一次)
function replaceKeywordsOnce(string $html, array $keywords): string {
    // 先保護 HTML 屬性值
    $placeholderMap = [];
    $html = preg_replace_callback('/\b(alt|title|href|src)=(["\'])(.*?)\2/i', function($m) use (&$placeholderMap) {
        $ph = '@@PROTECT_'.count($placeholderMap).'@@';
        $placeholderMap[$ph] = $m[0];
        return $ph;
    }, $html);

    foreach ($keywords as $kw => $link) {
        $html = replaceFirstOccurrence($html, $kw, $link);
    }

    // 還原屬性
    return strtr($html, $placeholderMap);
}

$html = '<img alt="Apple"> Apple 發布 Apple Watch';
$map = ['Apple' => '<a href="/apple">Apple</a>'];
echo replaceKeywordsOnce($html, $map);
// 輸出:<img alt="Apple"> <a href="/apple">Apple</a> 發布 Apple Watch
?>

三、方案二:strpos + substr_replace(無正則、記憶體友善)

<?php
function replaceFirstStrpos(string &$haystack, string $needle, string $replace, bool $caseInsensitive = true): bool {
    $pos = $caseInsensitive ? stripos($haystack, $needle) : strpos($haystack, $needle);
    if ($pos === false) return false;
    $haystack = substr_replace($haystack, $replace, $pos, strlen($needle));
    return true;
}

// 批次版本
function replaceKeywordsOnceStrpos(string $html, array $map): string {
    // 同樣需先保護屬性,略
    foreach ($map as $kw => $repl) {
        replaceFirstStrpos($html, $kw, $repl, true);
    }
    return $html;
}
?>

四、方案三:DOMDocument 解析僅替換文本節點(最安全、最嚴謹)

<?php
function replaceInTextNodes(\DOMDocument $dom, string $search, string $replace, bool $caseInsensitive = true): int {
    $xpath = new \DOMXPath($dom);
    $textNodes = $xpath->query('//text()');
    $replaced = 0;
    foreach ($textNodes as $textNode) {
        $original = $textNode->nodeValue;
        $flags = $caseInsensitive ? PREG_OFFSET_CAPTURE : 0;
        $pattern = '/'.preg_quote($search, '/').'/'.($caseInsensitive ? 'i' : '');
        if (preg_match($pattern, $original, $matches, $flags)) {
            $offset = $matches[0][1] ?? 0;
            $newValue = substr_replace($original, $replace, $offset, strlen($matches[0][0]));
            $textNode->nodeValue = $newValue;
            $replaced++;
            if ($replaced >= 1) break; // 全域僅第一次
        }
    }
    return $replaced;
}

function replaceKeywordsDom(string $html, array $map): string {
    $dom = new \DOMDocument();
    $dom->loadHTML(''.$html, LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD);
    foreach ($map as $kw => $repl) {
        replaceInTextNodes($dom, $kw, $repl);
    }
    return $dom->saveHTML();
}
?>

五、方案比較與選型建議

方案速度記憶體HTML 安全適用場景
preg_replace_callback快低需手動保護屬性大量文本、關鍵字少、可接受簡單保護
strpos + substr_replace最快最低需手動保護屬性極大文本、單關鍵字、效能敏感
DOMDocument慢高原生安全複雜 HTML、需嚴格保證標籤完整、關鍵字多

六、進階:限制每關鍵字替換 N 次

<?php
function replaceLimited(string $subject, string $search, string $replace, int $limit, bool $caseInsensitive = true): string {
    if ($limit <= 0) return $subject;
    $pattern = '/'.preg_quote($search, '/').'/'.($caseInsensitive ? 'i' : '');
    $count = 0;
    return preg_replace_callback($pattern, function($m) use (&$count, $limit, $replace) {
        return ++$count <= $limit ? $replace : $m[0];
    }, $subject);
}
?>
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved