BlankPlaceholderRenderer.php 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121
  1. <?php
  2. namespace App\Support;
  3. class BlankPlaceholderRenderer
  4. {
  5. private const DEFAULT_BLANK_SPAN = '<span style="display:inline-block; min-width:80px; border-bottom:1.2px dashed #444; vertical-align:bottom;">&nbsp;</span>';
  6. /**
  7. * 将题干中的空括号/下划线/部分异常占位符统一替换为标准空位样式。
  8. *
  9. * @return array{0:string,1:bool} [renderedContent, replacedAnyPlaceholder]
  10. */
  11. public static function replaceToBlankSpan(
  12. string $content,
  13. ?string $blankSpan = null,
  14. bool $collapseAdjacentBlanks = false,
  15. bool $normalizeChineseTerminalPeriod = true
  16. ): array
  17. {
  18. $blankSpan = $blankSpan ?: self::DEFAULT_BLANK_SPAN;
  19. $renderedContent = $content;
  20. $latexPlaceholders = [];
  21. $counter = 0;
  22. $renderedContent = preg_replace_callback('/\$(?:[^\$]|\\\\.)*\$/u', function ($matches) use (&$latexPlaceholders, &$counter, $blankSpan) {
  23. $latexContent = $matches[0];
  24. $inner = mb_substr($latexContent, 1, mb_strlen($latexContent) - 2);
  25. // 数学环境内也可能包含填空占位符(如 $\\underline{\\qquad}$ / $\\angle A=\\underline{\\quad}$)
  26. $blankToken = '<<<BLANK_IN_MATH_'.$counter.'>>>';
  27. $innerWithBlanks = preg_replace(
  28. [
  29. '/\\\\underline\{[^}]*\}/u',
  30. '/\\\\qquad+/u',
  31. '/\\\\quad+/u',
  32. '/[((](?:\s|&nbsp;|&#160;| )*[))]/u',
  33. '/_{2,}/u',
  34. ],
  35. $blankToken,
  36. $inner,
  37. -1,
  38. $blankCount
  39. );
  40. if ($blankCount > 0) {
  41. $parts = explode($blankToken, $innerWithBlanks);
  42. $rebuilt = '';
  43. $lastIndex = count($parts) - 1;
  44. foreach ($parts as $index => $part) {
  45. if ($part !== '') {
  46. $rebuilt .= htmlspecialchars('$'.$part.'$', ENT_QUOTES | ENT_HTML5, 'UTF-8');
  47. }
  48. if ($index < $lastIndex) {
  49. $rebuilt .= $blankSpan;
  50. }
  51. }
  52. return $rebuilt === '' ? $blankSpan : $rebuilt;
  53. }
  54. $placeholder = '<<<LATEX_BLANK_'.$counter.'>>>';
  55. $latexPlaceholders[$placeholder] = $latexContent;
  56. $counter++;
  57. return $placeholder;
  58. }, $renderedContent);
  59. // 兼容常见空位写法:\underline{...}、\qquad、空括号(含 nbsp 等空白)、连续下划线、尾部 \\$
  60. $patterns = [
  61. '/\\\underline\{[^}]*\}/u',
  62. '/\\\qquad+/u',
  63. '/[((](?:\s|&nbsp;|&#160;| )*[))]/u',
  64. '/_{2,}/u',
  65. '/\\\\+\$(?=\s*$)/u',
  66. ];
  67. $renderedContent = preg_replace($patterns, $blankSpan, $renderedContent);
  68. if ($collapseAdjacentBlanks) {
  69. $quotedBlankSpan = preg_quote($blankSpan, '/');
  70. $renderedContent = preg_replace('/(?:'.$quotedBlankSpan.'(?:\s|&nbsp;|&#160;| )*){2,}/u', $blankSpan, $renderedContent);
  71. }
  72. foreach ($latexPlaceholders as $placeholder => $latexContent) {
  73. if (preg_match('/^\$(.*?)(\\\\+)\$$/u', $latexContent, $match)) {
  74. $inner = rtrim($match[1]);
  75. if ($inner === '' || preg_match('/[=::]\s*$/u', $inner)) {
  76. if ($inner === '') {
  77. $replacement = $blankSpan;
  78. } else {
  79. $replacement = htmlspecialchars('$'.$inner.'$', ENT_QUOTES | ENT_HTML5, 'UTF-8').' '.$blankSpan;
  80. }
  81. $renderedContent = str_replace($placeholder, $replacement, $renderedContent);
  82. continue;
  83. }
  84. }
  85. $encodedLatex = htmlspecialchars($latexContent, ENT_QUOTES | ENT_HTML5, 'UTF-8');
  86. $renderedContent = str_replace($placeholder, $encodedLatex, $renderedContent);
  87. }
  88. if ($normalizeChineseTerminalPeriod) {
  89. $renderedContent = self::normalizeChineseTerminalPeriod($renderedContent);
  90. }
  91. return [$renderedContent, $renderedContent !== $content];
  92. }
  93. public static function defaultBlankSpan(): string
  94. {
  95. return self::DEFAULT_BLANK_SPAN;
  96. }
  97. private static function normalizeChineseTerminalPeriod(string $content): string
  98. {
  99. // 仅在存在中文语境时,把句末英文句号统一为中文句号。
  100. if (! preg_match('/\p{Han}/u', $content)) {
  101. return $content;
  102. }
  103. return preg_replace('/[\..](?=\s*(?:<\/[^>]+>\s*)*$)/u', '。', $content);
  104. }
  105. }