单反斜杠 // 数据库存储时 \sqrt 变成 \\sqrt,需要还原 $content = self::normalizeBackslashesInDelimiters($content); // 2. 如果内容中包含定界符,清理内部 HTML if (self::containsDelimiters($content)) { $content = self::cleanInsideDelimiters($content); } // 3. 检测内容类型:纯数学、混合内容还是纯文本 $contentType = self::detectContentType($content); // 4. 根据内容类型采取不同的处理策略 switch ($contentType) { case 'pure_math': // 纯数学表达式,如 "4x^2 - 25y^2" 或 "f(x) = x^2 - 4x + 5" return self::wrapPureMath($content); case 'mixed_content': // 混合内容,如 "已知函数 f(x) = x^2 - 4x + 5,求最小值" return self::smartWrapMixedContent($content); case 'plain_text': default: // 纯文本,不需要处理 return $content; } } /** * 【新增】将公式定界符内的双反斜杠转为单反斜杠 * 与前端 MathText.tsx 的 preprocessText 逻辑保持一致 */ private static function normalizeBackslashesInDelimiters(string $content): string { // 1. 处理 $$...$$ 块级公式内的双反斜杠 $content = preg_replace_callback('/\$\$([\s\S]*?)\$\$/', function ($matches) { $tex = str_replace('\\\\', '\\', $matches[1]); return '$$' . $tex . '$$'; }, $content); // 2. 处理 $...$ 行内公式内的双反斜杠(避免与$$冲突) $content = preg_replace_callback('/(?]+>'; $existingDelimiterPattern = '(?:\$\$[\s\S]*?\$\$|\$[\s\S]*?\$|\\\\\([\s\S]*?\\\\\)|\\\\\[[\s\S]*?\\\\\])'; // 数学公式模式(按优先级排列) $patterns = [ // 1. 函数定义: f(x) = 2x^3 - 3x^2 + 4x - 5 "[a-zA-Z]'?\\([a-zA-Z0-9,\\s]+\\)\\s*=\\s*[a-zA-Z0-9\\+\\-\\*\\/\\^\\s\\.\\(\\)\\_\\{\\}]+", // 2. 导数/函数调用: f'(1), g(5), sin(x) "[a-zA-Z]+'?\\([a-zA-Z0-9\\+\\-\\*\\/\\^\\s\\.]+\\)", // 3. LaTeX 命令: \frac{1}{2} "\\\\[a-zA-Z]+\\{[^}]*\\}(?:\\{[^}]*\\})?", // 4. 数学表达式: x^2 + y^2, 2x - 3 "[a-zA-Z0-9]+[\\^_][a-zA-Z0-9\\{\\}]+(?:\\s*[\\+\\-\\*\\/]\\s*[a-zA-Z0-9\\^_\\{\\}\\.]+)*", ]; $mathPattern = '(?:' . implode('|', $patterns) . ')'; $pattern = "/($tagPattern)|($existingDelimiterPattern)|($mathPattern)/u"; return preg_replace_callback($pattern, function ($matches) { // HTML 标签,原样返回 if (!empty($matches[1])) { return $matches[1]; } // 已有的定界符,原样返回 if (!empty($matches[2])) { return $matches[2]; } // 数学公式,添加 $ 包裹 if (!empty($matches[3])) { $math = trim($matches[3]); // 再次检查是否已经包裹 if (str_contains($math, '$')) { return $math; } return '$' . $math . '$'; } return $matches[0]; }, $content); } /** * 检查是否已有定界符 */ private static function hasDelimiters(string $content): bool { $content = trim($content); // 检查 $$...$$ if (str_starts_with($content, '$$') && str_ends_with($content, '$$')) { return true; } // 检查 $...$ if (str_starts_with($content, '$') && str_ends_with($content, '$')) { return true; } // 检查 \[...\] if (str_starts_with($content, '\\[') && str_ends_with($content, '\\]')) { return true; } // 检查 \(...\) if (str_starts_with($content, '\\(') && str_ends_with($content, '\\)')) { return true; } return false; } /** * 检测数学特征 * 优化:更精确的检测,减少误判 */ private static function containsMathFeatures(string $content): bool { // 1. 检查是否有 LaTeX 命令 if (preg_match('/\\\\[a-zA-Z]+\{?/', $content)) { return true; } // 2. 检查函数定义或等式(如 f(x) =, g(x) =) // 必须是:字母+括号+等号+数学内容 if (preg_match('/[a-zA-Z]\([a-zA-Z0-9,\s]+\)\s*=\s*[a-zA-Z0-9\+\-\*\/\^\.\(\)\s\\\\_\{]+/', $content)) { return true; } // 3. 检查纯数学表达式(只包含数字、变量、运算符、括号) // 严格的数学表达式:必须包含字母和运算符,且没有中文字符 if (preg_match('/^[a-zA-Z0-9\+\-\*\/\=\s\.\^\(\)\_\{\}]+$/', $content) && preg_match('/[a-zA-Z]/', $content) && preg_match('/[\+\-\*\/\=\^]/', $content)) { return true; } // 4. 检查包含变量的数学表达式(带约束) // 必须有明确的运算符连接,且周围是数学内容 if (preg_match('/[a-zA-Z0-9\.\^\_\{\}]\s*[\+\-\*\/]\s*[a-zA-Z0-9\.\^\_\{\}\(\)]/', $content)) { return true; } // 5. 检查分数形式(如 \frac{}{}) if (preg_match('/\\\\frac\{/', $content)) { return true; } // 6. 检查上标或下标(仅当与数字/字母组合时) if (preg_match('/[a-zA-Z0-9]\s*[\^_]\s*[a-zA-Z0-9]/', $content)) { return true; } return false; } /** * 批量处理 */ public static function processArray(array $data, array $fieldsToProcess): array { foreach ($data as $key => &$value) { if (in_array($key, $fieldsToProcess) && is_string($value)) { $value = self::processFormulas($value); } elseif (is_array($value)) { $value = self::processArray($value, $fieldsToProcess); } } return $data; } /** * 处理题目数据 */ public static function processQuestionData(array $question): array { $fieldsToProcess = [ 'stem', 'content', 'question_text', 'answer', 'correct_answer', 'student_answer', 'explanation', 'solution', 'question_content', 'options' ]; return self::processArray($question, $fieldsToProcess); } /** * 修复被污染的数学公式(包含重复的转义字符) */ private static function fixCorruptedFormulas(string $content): string { // 简化的修复策略,只处理明确的问题 // 1. 将超过2个连续的$符号减少为2个 $content = preg_replace('/\${3,}/', '$$', $content); // 2. 修复$$B . - \frac{1}{2}$$ 这种格式,在选项前加空格 $content = preg_replace('/\$\$([A-Z])\s*\.\s*/', '$$ $1. ', $content); // 3. 修复不完整的frac命令:\frac{1}{2} -> \frac{1}{2} $content = preg_replace('/\\\\frac\\\\({[^}]+)([^}]*)\\\\/', '\\\\frac$1}{$2}', $content); // 4. 移除孤立的反斜杠(在非LaTeX命令前的) $content = preg_replace('/\\\\(?![a-zA-Z{])/', '', $content); return $content; } }