TemplateParser.php 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160
  1. <?php
  2. // ================== 主解析器 ==================
  3. namespace App\Services\Template;
  4. use App\Services\Template\Contracts\ParserInterface;
  5. class TemplateParser implements ParserInterface
  6. {
  7. private TemplateRegistry $registry;
  8. private ParameterResolver $parameterResolver;
  9. public function __construct()
  10. {
  11. $this->registry = new TemplateRegistry;
  12. $this->parameterResolver = new ParameterResolver($this->registry);
  13. }
  14. public function parse(string $content): ParsedDocument
  15. {
  16. $tokenizer = new TemplateTokenizer($content);
  17. $tokens = $tokenizer->tokenize();
  18. $nodes = [];
  19. $templatesUsed = [];
  20. foreach ($tokens as $token) {
  21. if ($token['type'] === 'text') {
  22. $nodes[] = new TextNode($token['content'], $token['position']);
  23. } elseif ($token['type'] === 'template') {
  24. $templateNode = $this->parseTemplateContent($token);
  25. if ($templateNode) {
  26. $nodes[] = $templateNode;
  27. $templatesUsed[] = $templateNode->name;
  28. } else {
  29. // 解析失败,当作文本处理
  30. $nodes[] = new TextNode($token['raw'], $token['position']);
  31. }
  32. }
  33. }
  34. return new ParsedDocument($nodes, [
  35. 'templates_used' => array_unique($templatesUsed),
  36. 'total_templates' => count($templatesUsed),
  37. ]);
  38. }
  39. private function parseTemplateContent(array $token): ?TemplateNode
  40. {
  41. $content = $token['content'];
  42. $parts = $this->splitTemplateParts($content);
  43. if (empty($parts)) {
  44. return null;
  45. }
  46. $templateName = array_shift($parts);
  47. $rawParams = $this->parseParameters($parts);
  48. // 递归解析参数中的嵌套模板
  49. $processedParams = [];
  50. foreach ($rawParams as $key => $value) {
  51. if (strpos($value, '{{') !== false) {
  52. // 参数值包含模板,递归解析
  53. $subDocument = $this->parse($value);
  54. $processedParams[$key] = $subDocument->content;
  55. } else {
  56. $processedParams[$key] = $value;
  57. }
  58. }
  59. $resolvedParams = $this->parameterResolver->resolveParameters($templateName, $processedParams);
  60. return new TemplateNode(
  61. $templateName,
  62. $resolvedParams,
  63. [],
  64. $token['raw'],
  65. $token['position']
  66. );
  67. }
  68. private function splitTemplateParts(string $content): array
  69. {
  70. $parts = [];
  71. $current = '';
  72. $braceLevel = 0;
  73. $inQuotes = false;
  74. $quoteChar = '';
  75. for ($i = 0; $i < strlen($content); $i++) {
  76. $char = $content[$i];
  77. $nextChar = $i + 1 < strlen($content) ? $content[$i + 1] : '';
  78. if (! $inQuotes && ($char === '"' || $char === "'")) {
  79. $inQuotes = true;
  80. $quoteChar = $char;
  81. $current .= $char;
  82. } elseif ($inQuotes && $char === $quoteChar) {
  83. $inQuotes = false;
  84. $quoteChar = '';
  85. $current .= $char;
  86. } elseif (! $inQuotes && $char === '{' && $nextChar === '{') {
  87. $braceLevel++;
  88. $current .= '{{';
  89. $i++; // 跳过下一个字符
  90. } elseif (! $inQuotes && $char === '}' && $nextChar === '}') {
  91. $braceLevel--;
  92. $current .= '}}';
  93. $i++; // 跳过下一个字符
  94. } elseif (! $inQuotes && $char === '|' && $braceLevel === 0) {
  95. $parts[] = trim($current);
  96. $current = '';
  97. } else {
  98. $current .= $char;
  99. }
  100. }
  101. if ($current !== '') {
  102. $parts[] = trim($current);
  103. }
  104. return $parts;
  105. }
  106. private function parseParameters(array $parts): array
  107. {
  108. $params = [];
  109. $positionalIndex = 0;
  110. foreach ($parts as $part) {
  111. if (strpos($part, '=') !== false && ! $this->isInNestedTemplate($part)) {
  112. // 命名参数
  113. [$key, $value] = explode('=', $part, 2);
  114. $params[trim($key)] = trim($value);
  115. } else {
  116. // 位置参数
  117. $params[$positionalIndex] = trim($part);
  118. $positionalIndex++;
  119. }
  120. }
  121. return $params;
  122. }
  123. private function isInNestedTemplate(string $content): bool
  124. {
  125. $openBraces = substr_count($content, '{{');
  126. $closeBraces = substr_count($content, '}}');
  127. return $openBraces > 0 && $openBraces >= $closeBraces;
  128. }
  129. public function getRegistry(): TemplateRegistry
  130. {
  131. return $this->registry;
  132. }
  133. }