| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153 |
- <?php
- namespace App\Support;
- use App\Services\MathFormulaProcessor;
- /**
- * 与判卷页 components/exam/paper-body 选择题题干处理口径一致:
- * 去题号前缀、separateStemAndOptions 分离选项、填空占位虚线、MathFormulaProcessor::processFormulas
- */
- class GradingStyleQuestionStem
- {
- /**
- * 选项字母映射由下方 separateStemAndOptions / extractOptions 从正文解析;与判卷页一致。
- */
- public static function buildChoiceStemForReport(string $rawQuestionText): string
- {
- $html = is_string($rawQuestionText) ? $rawQuestionText : '';
- $cleanContent = preg_replace('/^\d+[\.、]\s*/', '', trim($html));
- [$stemLine] = self::separateStemAndOptions($cleanContent);
- $renderedStem = self::applyBlankPlaceholdersLikeGrading($stemLine);
- return MathFormulaProcessor::processFormulas($renderedStem);
- }
- /**
- * 与 ExamPdfController::separateStemAndOptions 一致
- *
- * @return array{0: string, 1: array<int, string>}
- */
- public static function separateStemAndOptions(string $content): array
- {
- $contentWithoutSvg = preg_replace('/<svg[^>]*>.*?<\/svg>/is', '[SVG_PLACEHOLDER]', $content);
- $hasOptions = preg_match('/(?:^|\s)[A-D][\.、:.:]/u', $contentWithoutSvg);
- if (! $hasOptions) {
- return [$content, []];
- }
- $options = self::extractOptions($content);
- if (! empty($options)) {
- if (preg_match('/^(.+?)(?=(?:^|\s)[A-D][\.、:.:])/su', $contentWithoutSvg, $match)) {
- $stem = trim($match[1]);
- if (strpos($stem, '[SVG_PLACEHOLDER]') !== false) {
- foreach (['A.', 'A、', 'A:', 'A.', 'A:'] as $marker) {
- if (preg_match('/\s'.preg_quote($marker, '/').'/', $content, $m, PREG_OFFSET_CAPTURE)) {
- $pos = $m[0][1];
- if ($pos > 0) {
- $stem = trim(mb_substr($content, 0, $pos));
- break;
- }
- }
- }
- }
- } else {
- $stem = $content;
- foreach (['A.', 'A、', 'A:', 'A.', 'A:'] as $marker) {
- if (preg_match('/\s'.preg_quote($marker, '/').'/', $content, $m, PREG_OFFSET_CAPTURE)) {
- $pos = $m[0][1];
- if ($pos > 0) {
- $stem = trim(mb_substr($content, 0, $pos));
- break;
- }
- }
- }
- }
- $stem = preg_replace('/()\s*$/', '', $stem);
- $stem = trim($stem);
- return [$stem, $options];
- }
- return [$content, []];
- }
- /**
- * 与 ExamPdfController::extractOptions 一致
- *
- * @return array<int, string>
- */
- public static function extractOptions(string $content): array
- {
- $options = [];
- $contentWithoutSvg = preg_replace('/<svg[^>]*>.*?<\/svg>/is', '[SVG_PLACEHOLDER]', $content);
- $pattern = '/(?:^|\s)([A-D])[\.、:.:]\s*(.+?)(?=(?:^|\s)[A-D][\.、:.:]|$)/su';
- if (preg_match_all($pattern, $contentWithoutSvg, $matches, PREG_SET_ORDER)) {
- foreach ($matches as $match) {
- $optionText = trim($match[2]);
- $optionText = preg_replace('/\s+$/', '', $optionText);
- $optionText = preg_replace('/^\$\$\s*/', '', $optionText);
- $optionText = preg_replace('/\s*\$\$$/', '', $optionText);
- if (! empty($optionText)) {
- $options[] = $optionText;
- }
- }
- }
- if (empty($options)) {
- $lines = preg_split('/[\r\n]+/', $contentWithoutSvg);
- foreach ($lines as $line) {
- $line = trim($line);
- if (preg_match('/^([A-D])[\.、:.:]\s*(.+)$/u', $line, $match)) {
- $optionText = trim($match[2]);
- if (! empty($optionText)) {
- $options[] = $optionText;
- }
- }
- }
- }
- return $options;
- }
- /**
- * 与 paper-body 选择题中 $renderedStem 逻辑一致
- */
- private static function applyBlankPlaceholdersLikeGrading(string $stemLine): string
- {
- $blankSpan = '<span style="display:inline-block; min-width:80px; border-bottom:1.2px dashed #444; vertical-align:bottom;"> </span>';
- $renderedStem = $stemLine;
- $renderedStem = preg_replace('/\\\underline\{[^}]*\}/', $blankSpan, $renderedStem);
- $renderedStem = preg_replace('/\\\qquad+/', $blankSpan, $renderedStem);
- $latexPlaceholders = [];
- $counter = 0;
- $renderedStem = preg_replace_callback('/\$[^$]+\$/u', function ($matches) use (&$latexPlaceholders, &$counter) {
- $placeholder = '<<<LATEX_'.$counter.'>>>';
- $latexPlaceholders[$placeholder] = $matches[0];
- $counter++;
- return $placeholder;
- }, $renderedStem);
- $renderedStem = preg_replace(['/(\s*)/u', '/\(\s*\)/', '/_{2,}/'], $blankSpan, $renderedStem);
- foreach ($latexPlaceholders as $placeholder => $latexContent) {
- $encodedLatex = htmlspecialchars($latexContent, ENT_QUOTES | ENT_HTML5, 'UTF-8');
- $renderedStem = str_replace($placeholder, $encodedLatex, $renderedStem);
- }
- if ($renderedStem === $stemLine) {
- $renderedStem .= ' '.$blankSpan;
- }
- return $renderedStem;
- }
- }
|