|
@@ -15,24 +15,78 @@ class GradingMarkBoxCounter
|
|
|
return max(1, $count);
|
|
return max(1, $count);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- public function countAnswerSteps(?string $text): int
|
|
|
|
|
|
|
+ public function countAnswerSteps(?string $text, ?string $stem = null): int
|
|
|
{
|
|
{
|
|
|
- $text = (string) $text;
|
|
|
|
|
- $stepPattern = '(步骤\s*[0-9一二三四五六七八九十百零两]+\s*[::]?|第\s*[0-9一二三四五六七八九十百零两]+\s*步\s*[::]?)';
|
|
|
|
|
|
|
+ $stepCount = $this->countExplicitAnswerSteps($text);
|
|
|
|
|
+ if ($stepCount > 0) {
|
|
|
|
|
+ return $stepCount;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return $this->inferSubQuestionCount($stem);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 判题卡解答题方框数:
|
|
|
|
|
+ * - 优先按题干小题编号系列计数((1)(2)...)
|
|
|
|
|
+ * - 若不存在系列小题,则固定 1 框(避免被解析步骤数放大)
|
|
|
|
|
+ */
|
|
|
|
|
+ public function countAnswerMarkBoxes(?string $stem, ?string $solution = null): int
|
|
|
|
|
+ {
|
|
|
|
|
+ $minFromStem = $this->inferSubQuestionCount($stem);
|
|
|
|
|
+ $stepCount = $this->countExplicitAnswerSteps($solution);
|
|
|
|
|
+
|
|
|
|
|
+ return max($minFromStem, $stepCount > 0 ? $stepCount : 1);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 仅识别“显式步骤标记”并计数:
|
|
|
|
|
+ * - 步骤1: / 步骤一:
|
|
|
|
|
+ * - 第1步: / 第一步:
|
|
|
|
|
+ * 要求位于段首(行首或标点后),降低误判。
|
|
|
|
|
+ */
|
|
|
|
|
+ private function countExplicitAnswerSteps(?string $text): int
|
|
|
|
|
+ {
|
|
|
|
|
+ $text = trim((string) $text);
|
|
|
|
|
+ if ($text === '') {
|
|
|
|
|
+ return 0;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ $text = preg_replace('/<br\s*\/?>/iu', "\n", $text) ?? $text;
|
|
|
|
|
+ $stepLabelPattern = '(步骤\s*[0-9一二三四五六七八九十百零两]+\s*[::]?)';
|
|
|
|
|
+ $anchorPattern = '/(?:^|[\r\n。;;!?!?])\s*' . $stepLabelPattern . '/u';
|
|
|
|
|
+ preg_match_all($anchorPattern, $text, $matches);
|
|
|
|
|
+ $count = count($matches[0] ?? []);
|
|
|
|
|
|
|
|
- if (!preg_match('/' . $stepPattern . '/u', $text)) {
|
|
|
|
|
|
|
+ return max(0, $count);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 当解析里没有步骤时,按题干中的小题“系列编号”估算判题卡方框数。
|
|
|
|
|
+ * 仅在至少出现 2 个编号时生效,避免误把 f(1) 这类数学表达当作小题。
|
|
|
|
|
+ */
|
|
|
|
|
+ private function inferSubQuestionCount(?string $stem): int
|
|
|
|
|
+ {
|
|
|
|
|
+ $stem = (string) $stem;
|
|
|
|
|
+ if ($stem === '') {
|
|
|
return 1;
|
|
return 1;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- $parts = preg_split('/(?=' . $stepPattern . ')/u', $text, -1, PREG_SPLIT_NO_EMPTY) ?: [];
|
|
|
|
|
- $count = 0;
|
|
|
|
|
- foreach ($parts as $part) {
|
|
|
|
|
- $stepText = trim((string) $part);
|
|
|
|
|
- if ($stepText !== '' && preg_match('/^' . $stepPattern . '/u', $stepText)) {
|
|
|
|
|
- $count++;
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ preg_match_all('/[((][1-9][0-9]*[))]/u', $stem, $allMarkers);
|
|
|
|
|
+ $markerCount = count($allMarkers[0] ?? []);
|
|
|
|
|
+ if ($markerCount < 2) {
|
|
|
|
|
+ return 1;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- return max(1, $count);
|
|
|
|
|
|
|
+ // 与题干显示层保持一致:把小题起始语境标准化到 <br>(n)
|
|
|
|
|
+ $normalized = preg_replace('/^\s*([((][1-9][0-9]*[))])\s*/u', '$1 ', $stem) ?? $stem;
|
|
|
|
|
+ $normalized = preg_replace('/([。;;!?!?::.])\s*([((][1-9][0-9]*[))])\s*/u', '$1<br>$2 ', $normalized) ?? $normalized;
|
|
|
|
|
+ $normalized = preg_replace('/(?:(?:\\\\r\\\\n|\\\\n)|(?:\r?\n)|(?:<br\s*\/?>)|\s)+\s*([((][1-9][0-9]*[))])\s*/u', '<br>$1 ', $normalized) ?? $normalized;
|
|
|
|
|
+ $normalized = preg_replace('/(求出|求解|求|写出|计算|证明|判断|化简)\s*([((][1-9][0-9]*[))])\s*/u', '$1<br>$2 ', $normalized) ?? $normalized;
|
|
|
|
|
+
|
|
|
|
|
+ // 统计“行首或换行后”的编号数量,作为子题数量
|
|
|
|
|
+ preg_match_all('/(?:^|<br>\s*)([((][1-9][0-9]*[))])/u', $normalized, $series);
|
|
|
|
|
+ $seriesCount = count($series[1] ?? []);
|
|
|
|
|
+
|
|
|
|
|
+ return max(1, $seriesCount);
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|