function yang_keyword_link($content, $keyword, $link) {
// 1. 保護圖片屬性中的關鍵詞
$content = preg_replace('|(<img[^>]*?)(' . $keyword . ')([^>]*?>)|U', '$1%&&&&&%$3', $content);
// 2. 替換非標簽內的首次出現
$regex = '/(?!((<.*?)|(<a.*?)))(' . $keyword . ')(?!(([^<>]*?)>)|([^>]*?<\/a>))/si';
$url = '<a href="' . $link . '">' . $keyword . '</a>';
$content = preg_replace($regex, $url, $content, 1);
// 3. 還原圖片關鍵詞
return str_replace('%&&&&&%', $keyword, $content);
}
優點:跳過已有鏈接、圖片 alt/title、標簽屬性,僅替換首次純文本出現。
class InnerLink {
private static $spotUrlMap;
public static function genSpotLink($basePath, $description) {
if (empty(self::$spotUrlMap)) self::$spotUrlMap = DBViewSpot::getSpotPare(); // 從 DB 獲取映射
if (empty($description)) return $description;
foreach (self::$spotUrlMap as $pair) {
$replace = "<a target='_blank' href='$basePath/{$pair[1]}.html'>{$pair[0]}</a>";
$description = self::replaceOnceOutsideTags($description, $pair[0], $replace);
}
return $description;
}
private static function replaceOnceOutsideTags($text, $needle, $replace) {
// 簡易實現:按 <a> 分割,僅在非鏈接段落替換首次
$parts = explode('<a', $text);
$replaced = false;
foreach ($parts as &$part) {
$sub = explode('</a>', $part);
if (count($sub) > 1) {
if ($sub[0][0] !== 'a' && $sub[0][0] !== 'A' && !$replaced) {
$sub[1] = self::strReplaceOnce($needle, $replace, $sub[1], $replaced);
}
$part = implode('</a>', $sub);
} elseif (!$replaced) {
$part = self::strReplaceOnce($needle, $replace, $part, $replaced);
}
}
return implode('<a', $parts);
}
private static function strReplaceOnce($needle, $replace, $haystack, &$replaced) {
$pos = strpos($haystack, $needle);
if ($pos === false) return $haystack;
$replaced = true;
return substr_replace($haystack, $replace, $pos, strlen($needle));
}
}
$keys = [
['網頁特效','/js_a/js.html'], ['seo','/seo/seo.html'], ['php','/phper/php.html'],
// ...
];
$str = "今天學php好,jsp也行,css+div...";
foreach ($keys as $k) {
if (strpos($str, $k[0]) !== false) {
$str = str_replace($k[0], "<a href='https://example.com{$k[1]}' target='_blank'>{$k[0]}</a>", $str);
}
}
echo $str;
缺陷:重複替換(已加鏈接的又被替換)、破壞標簽屬性、大小寫敏感、無首次限制。
| 難點 | 方案一 | 方案二 |
|---|---|---|
| 避開已有 <a> 標簽 | 負向前瞻/後瞻正則 | 按 <a>/</a> 分段處理 |
| 避開圖片屬性 | 預佔位符保護 | 同左 |
| 僅替換首次 | preg_replace limit=1 | 標記變量控制 |
| 大小寫不敏感 | regex /i 修飾符 | strpos 大小寫敏感,需 stripos |
<!-- noauto -->)供編輯控制。方案一正則法最適合單關鍵詞即時替換;方案二類封裝適合批量關鍵詞系統化管理;方案三僅供學習參考。核心原則:保護標簽結構、首次替換、大小寫不敏感、可配置化。