splitIntoParts($paper->raw_markdown); return DB::transaction(function () use ($paper, $parts) { $paper->parts()->delete(); $result = collect(); foreach ($parts as $idx => $part) { $result->push(PaperPart::create([ 'source_paper_id' => $paper->id, 'order' => $idx + 1, 'title' => $part['title'] ?? null, 'type' => $part['type'] ?? null, 'raw_markdown' => $part['raw'], 'question_count' => $part['question_count'] ?? null, 'detected_features' => $part['detected_features'] ?? [], ])); } return $result; }); } public function splitIntoParts(string $markdown): array { $lines = preg_split('/\r\n|\r|\n/', $markdown); $segments = []; $current = ['title' => null, 'buffer' => []]; $partPattern = '/^(#{2,3})\s*(第? ?[一二三四五六七八九十0-9IVX]+[部分卷]|选择题|填空题|解答题|综合题|计算题|应用题)/u'; $commentPattern = '//i'; foreach ($lines as $line) { $trimmed = trim($line); // 支持隐藏的区块标记 if (preg_match($commentPattern, $trimmed, $cm)) { if (!empty($current['buffer'])) { $segments[] = $this->finalizeSegment($current); } $current = [ 'title' => trim($cm[1]), 'buffer' => [$line], ]; continue; } if (preg_match($partPattern, $line, $m)) { if (!empty($current['buffer'])) { $segments[] = $this->finalizeSegment($current); } $current = [ 'title' => trim($m[0], "# \t"), 'buffer' => [$line], ]; } else { $current['buffer'][] = $line; } } if (!empty($current['buffer'])) { $segments[] = $this->finalizeSegment($current); } if (empty($segments)) { return [[ 'title' => null, 'type' => 'mixed', 'raw' => trim($markdown), 'detected_features' => [], ]]; } return $segments; } protected function finalizeSegment(array $segment): array { $raw = trim(implode("\n", $segment['buffer'])); $title = $segment['title']; return [ 'title' => $title, 'type' => $this->detectType($title), 'raw' => $raw, 'detected_features' => [ 'title' => $title, ], ]; } protected function detectType(?string $title): ?string { if (!$title) { return null; } return match (true) { Str::contains($title, '选择') => 'choice', Str::contains($title, '填空') => 'fill', Str::contains($title, ['解答', '简答', '分析']) => 'answer', Str::contains($title, ['计算', '推导']) => 'calc', default => 'mixed', }; } }