TemplateParser.php 4.8 KB

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