| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129 |
- <?php
- namespace App\Support;
- class BlankPlaceholderRenderer
- {
- private const DEFAULT_BLANK_SPAN = '<span style="display:inline-block; min-width:80px; border-bottom:1.2px dashed #444; vertical-align:bottom;"> </span>';
- /**
- * 将题干中的空括号/下划线/部分异常占位符统一替换为标准空位样式。
- *
- * @return array{0:string,1:bool} [renderedContent, replacedAnyPlaceholder]
- */
- public static function replaceToBlankSpan(
- string $content,
- ?string $blankSpan = null,
- bool $collapseAdjacentBlanks = false,
- bool $normalizeChineseTerminalPeriod = true
- ): array
- {
- $blankSpan = $blankSpan ?: self::DEFAULT_BLANK_SPAN;
- $renderedContent = $content;
- $latexPlaceholders = [];
- $counter = 0;
- $renderedContent = preg_replace_callback('/\$(?:[^\$]|\\\\.)*\$/u', function ($matches) use (&$latexPlaceholders, &$counter, $blankSpan) {
- $latexContent = $matches[0];
- $inner = mb_substr($latexContent, 1, mb_strlen($latexContent) - 2);
- // 数学环境内也可能包含填空占位符(如 $\\underline{\\qquad}$ / $\\angle A=\\underline{\\quad}$)
- $blankToken = '<<<BLANK_IN_MATH_'.$counter.'>>>';
- $innerWithBlanks = preg_replace(
- [
- '/\\\\underline\{[^}]*\}/u',
- '/\\\\qquad+/u',
- '/\\\\quad+/u',
- '/[((](?:\s| | | )*[))]/u',
- '/_{2,}/u',
- ],
- $blankToken,
- $inner,
- -1,
- $blankCount
- );
- if ($blankCount > 0) {
- $parts = explode($blankToken, $innerWithBlanks);
- $rebuilt = '';
- $lastIndex = count($parts) - 1;
- foreach ($parts as $index => $part) {
- if ($part !== '') {
- // 纯标点不再包进数学环境,避免生成 "$.$" 这类尾部格式。
- if (preg_match('/^[\..。]$/u', $part)) {
- $rebuilt .= $part;
- } else {
- $rebuilt .= htmlspecialchars('$'.$part.'$', ENT_QUOTES | ENT_HTML5, 'UTF-8');
- }
- }
- if ($index < $lastIndex) {
- $rebuilt .= $blankSpan;
- }
- }
- return $rebuilt === '' ? $blankSpan : $rebuilt;
- }
- $placeholder = '<<<LATEX_BLANK_'.$counter.'>>>';
- $latexPlaceholders[$placeholder] = $latexContent;
- $counter++;
- return $placeholder;
- }, $renderedContent);
- // 兼容常见空位写法:\underline{...}、\qquad、空括号(含 nbsp 等空白)、连续下划线、尾部 \\$
- $patterns = [
- '/\\\underline\{[^}]*\}/u',
- '/\\\qquad+/u',
- '/[((](?:\s| | | )*[))]/u',
- '/_{2,}/u',
- '/\\\\+\$(?=\s*$)/u',
- ];
- $renderedContent = preg_replace($patterns, $blankSpan, $renderedContent);
- if ($collapseAdjacentBlanks) {
- $quotedBlankSpan = preg_quote($blankSpan, '/');
- $renderedContent = preg_replace('/(?:'.$quotedBlankSpan.'(?:\s| | | )*){2,}/u', $blankSpan, $renderedContent);
- }
- foreach ($latexPlaceholders as $placeholder => $latexContent) {
- if (preg_match('/^\$(.*?)(\\\\+)\$$/u', $latexContent, $match)) {
- $inner = rtrim($match[1]);
- if ($inner === '' || preg_match('/[=::]\s*$/u', $inner)) {
- if ($inner === '') {
- $replacement = $blankSpan;
- } else {
- $replacement = htmlspecialchars('$'.$inner.'$', ENT_QUOTES | ENT_HTML5, 'UTF-8').' '.$blankSpan;
- }
- $renderedContent = str_replace($placeholder, $replacement, $renderedContent);
- continue;
- }
- }
- $encodedLatex = htmlspecialchars($latexContent, ENT_QUOTES | ENT_HTML5, 'UTF-8');
- $renderedContent = str_replace($placeholder, $encodedLatex, $renderedContent);
- }
- if ($normalizeChineseTerminalPeriod) {
- $renderedContent = self::normalizeChineseTerminalPeriod($renderedContent);
- }
- return [$renderedContent, $renderedContent !== $content];
- }
- public static function defaultBlankSpan(): string
- {
- return self::DEFAULT_BLANK_SPAN;
- }
- private static function normalizeChineseTerminalPeriod(string $content): string
- {
- // 仅在存在中文语境时,把句末英文句号统一为中文句号。
- if (! preg_match('/\p{Han}/u', $content)) {
- return $content;
- }
- // 先处理数学片段尾点(如 "$.$" / "$.$")。
- $content = preg_replace('/\$\s*[\..]\s*\$(?=\s*(?:<\/[^>]+>\s*)*$)/u', '。', $content);
- return preg_replace('/[\..](?=\s*(?:<\/[^>]+>\s*)*$)/u', '。', $content);
- }
- }
|