.*?<\/code>|.*?<\/pre>|`[^`]*`)/is';
$parts = preg_split($splitPattern, $text, -1, PREG_SPLIT_DELIM_CAPTURE);
if ($parts === false) {
// 分割失败,使用备用方法
$processed = self::replaceWithWordInterval($text, $pattern, $replacements, $options->skipChars, 0);
$text = $processed['text'];
} else {
$result = '';
$currentPos = 0;
foreach ($parts as $i => $part) {
// 奇数索引是匹配的部分(保持原样)
if ($i % 2 == 1) {
$result .= $part;
// 更新当前位置
$currentPos += mb_strlen(strip_tags($part), 'UTF-8');
} else {
// 偶数索引是非匹配文本,进行替换
$processed = self::replaceWithWordInterval($part, $pattern, $replacements, $options->skipChars, $currentPos);
$result .= $processed['text'];
$currentPos += $processed['char_count'];
}
}
$text = $result;
}
// 恢复被保护的标题
$text = self::restoreHeaders($text);
} else {
// 直接替换所有匹配(带间隔限制)
$processed = self::replaceWithWordInterval($text, $pattern, $replacements, $options->skipChars, 0);
$text = $processed['text'];
}
return $text;
}
/**
* 保护标题内容不被替换
*/
private static function protectHeaders($text, $excludeHeaders)
{
if (!is_array($excludeHeaders) || empty($excludeHeaders)) {
return $text;
}
$headerTags = array();
foreach ($excludeHeaders as $header) {
$headerTags[] = $header;
}
// 为每个标题标签创建唯一的占位符
$placeholderMap = array();
$placeholderIndex = 0;
foreach ($headerTags as $tag) {
// 匹配标题标签及其内容
$pattern = '/<(' . $tag . ')\b[^>]*>(.*?)<\/\1>/is';
$text = preg_replace_callback($pattern, function($matches) use (&$placeholderMap, &$placeholderIndex) {
$placeholder = "";
$placeholderMap[$placeholder] = $matches[0];
$placeholderIndex++;
return $placeholder;
}, $text);
}
// 将占位符映射存储在文本中(隐藏的注释)
if (!empty($placeholderMap)) {
$mapJson = json_encode($placeholderMap);
$text = "\n" . $text;
}
return $text;
}
/**
* 恢复被保护的标题内容
*/
private static function restoreHeaders($text)
{
// 查找并提取占位符映射
if (preg_match('//', $text, $mapMatch)) {
$mapJson = base64_decode($mapMatch[1]);
$placeholderMap = json_decode($mapJson, true);
if (is_array($placeholderMap)) {
// 替换所有占位符回原始内容
foreach ($placeholderMap as $placeholder => $original) {
$text = str_replace($placeholder, $original, $text);
}
// 移除映射注释
$text = preg_replace('/\n?/', '', $text);
}
}
return $text;
}
/**
* 带有相同词间隔限制的替换函数
*/
private static function replaceWithWordInterval($text, $pattern, &$replacements, $skipChars, $startPos)
{
$result = '';
$offset = 0;
$currentCharPos = $startPos;
// 使用preg_match_all获取所有匹配位置
if (preg_match_all($pattern, $text, $matches, PREG_OFFSET_CAPTURE)) {
foreach ($matches[0] as $match) {
$matchedWord = $match[0];
$matchPos = $match[1];
$matchLength = strlen($matchedWord);
// 计算匹配位置在当前文本中的字符位置
$textBeforeMatch = substr($text, 0, $matchPos);
$charPosInText = mb_strlen($textBeforeMatch, 'UTF-8');
// 实际在全文中的字符位置
$actualCharPos = $startPos + $charPosInText;
// 获取这个词的替换数据
$wordData = &$replacements[$matchedWord];
// 检查是否需要跳过(同一个词距离上次出现太近)
if ($actualCharPos - $wordData['last_pos'] < $skipChars) {
// 跳过这个匹配,直接添加到结果
if ($matchPos > $offset) {
$result .= substr($text, $offset, $matchPos - $offset);
}
$result .= $matchedWord; // 原样输出,不替换
$offset = $matchPos + $matchLength;
continue;
}
// 执行替换
if ($matchPos > $offset) {
$result .= substr($text, $offset, $matchPos - $offset);
}
$result .= $wordData['replace'];
$offset = $matchPos + $matchLength;
// 更新这个词的最后出现位置
$wordData['last_pos'] = $actualCharPos;
}
}
// 添加剩余文本
if ($offset < strlen($text)) {
$result .= substr($text, $offset);
}
// 计算处理后的文本字符数(不包括HTML标签)
$charCount = mb_strlen(strip_tags($result), 'UTF-8');
return array(
'text' => $result,
'char_count' => $charCount
);
}
/**
* 构建URL
*/
private static function buildUrl($format, $slug, $type = 'tag')
{
$options = Helper::options();
$siteUrl = rtrim($options->siteUrl, '/');
if (empty($slug)) {
return '#';
}
// 处理格式中的变量
$url = str_replace('{slug}', urlencode($slug), $format);
// 确保URL是完整的
if (strpos($url, 'http') !== 0 && strpos($url, '//') !== 0) {
if (strpos($url, '/') === 0) {
$url = $siteUrl . $url;
} else {
$url = $siteUrl . '/' . $url;
}
}
return $url;
}
}