标签转换为标准
标签
$content = self::convertImageTags($content);
// 1. 【关键修复】处理公式内的双反斜杠 -> 单反斜杠
// 数据库存储时 \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;
}
}
/**
* 将自定义 标签转换为标准
标签
* 例如: =>
*/
private static function convertImageTags(string $content): string
{
// 匹配 或 格式
return preg_replace(
'/|><\/image>)/i',
'
',
$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;
}
}