Issues (14)

src/Parser/AbstractBlock.php (1 issue)

Severity
1
<?php
2
3
/**
4
 * Platine Template
5
 *
6
 * Platine Template is a template engine that has taken a lot of inspiration from Django.
7
 *
8
 * This content is released under the MIT License (MIT)
9
 *
10
 * Copyright (c) 2020 Platine Template
11
 * Copyright (c) 2014 Guz Alexander, http://guzalexander.com
12
 * Copyright (c) 2011, 2012 Harald Hanek, http://www.delacap.com
13
 * Copyright (c) 2006 Mateo Murphy
14
 *
15
 * Permission is hereby granted, free of charge, to any person obtaining a copy
16
 * of this software and associated documentation files (the "Software"), to deal
17
 * in the Software without restriction, including without limitation the rights
18
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
19
 * copies of the Software, and to permit persons to whom the Software is
20
 * furnished to do so, subject to the following conditions:
21
 *
22
 * The above copyright notice and this permission notice shall be included in all
23
 * copies or substantial portions of the Software.
24
 *
25
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
26
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
27
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
28
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
29
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
30
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
31
 * SOFTWARE.
32
 */
33
34
/**
35
 *  @file AbstractBlock.php
36
 *
37
 *  The base Template block class
38
 *
39
 *  @package    Platine\Template\Parser
40
 *  @author Platine Developers Team
41
 *  @copyright  Copyright (c) 2020
42
 *  @license    http://opensource.org/licenses/MIT  MIT License
43
 *  @link   https://www.platine-php.com
44
 *  @version 1.0.0
45
 *  @filesource
46
 */
47
48
declare(strict_types=1);
49
50
namespace Platine\Template\Parser;
51
52
use InvalidArgumentException;
53
use Platine\Template\Exception\ParseException;
54
use Platine\Template\Exception\RenderException;
55
56
/**
57
 * @class AbstractBlock
58
 * @package Platine\Template\Parser
59
 */
60
class AbstractBlock extends AbstractTag
61
{
62
    /**
63
     * The node list
64
     * @var AbstractTag[]|Variable[]|string[]
65
     */
66
    protected array $nodeList = [];
67
68
    /**
69
     * Whenever next token should be ltrim'med.
70
     * @var bool
71
     */
72
    protected bool $trimWhitespace = false;
73
74
    /**
75
     * Return the node list
76
     * @return AbstractTag[]|Variable[]|string[]
77
     */
78
    public function getNodeList(): array
79
    {
80
        return $this->nodeList;
81
    }
82
83
    /**
84
     * {@inheritdoc}
85
    */
86
    public function parse(array &$tokens): void
87
    {
88
        $startRegex = '/^' . Token::BLOCK_OPEN . '/';
89
        $variableStartRegex = '/^' . Token::VARIABLE_OPEN . '/';
90
        $tagRegex = '/^'
91
                        . Token::BLOCK_OPEN
92
                        . Token::WHITESPACE_CONTROL
93
                        . '?\s*(\w+)\s*(.*?)'
94
                        . Token::WHITESPACE_CONTROL
95
                        . '?'
96
                        . Token::BLOCK_CLOSE
97
                        . '$/';
98
99
        $lexerStart = new Lexer($startRegex);
100
        $lexerVariableStart = new Lexer($variableStartRegex);
101
        $lexerTag = new Lexer($tagRegex);
102
103
        $this->nodeList = [];
104
        //Custom tags
105
        $tags = $this->parser->getTemplate()->getTags();
106
        while (count($tokens) > 0) {
107
            $token = (string) array_shift($tokens);
108
            if ($lexerStart->match($token)) {
109
                $this->whitespaceHandler($token);
110
                if ($lexerTag->match($token)) {
111
                    // If we found the proper block
112
                    // delimitor just end parsing here
113
                    // and let the outer block proceed
114
                    if ($lexerTag->getStringMatch(1) === $this->blockDelimiter()) {
115
                        $this->endTag();
116
                        return;
117
                    }
118
119
                    $node = null;
120
                    if (array_key_exists($lexerTag->getStringMatch(1), $tags)) {
121
                        $tagNameClass = $tags[$lexerTag->getStringMatch(1)];
122
                        if (is_string($tagNameClass)) {
123
                            $node = new $tagNameClass(
124
                                $lexerTag->getStringMatch(2),
125
                                $tokens,
126
                                $this->parser
127
                            );
128
                        } else {
129
                            $node = $tagNameClass;
130
                        }
131
                    } else {
132
                        //check for core tags
133
                        $coreTagName = $lexerTag->getStringMatch(1);
134
                        $coreTagNameClass = 'Platine\\Template\\Tag\\' . ucwords($coreTagName) . 'Tag';
135
                        if (class_exists($coreTagNameClass)) {
136
                            $node = new $coreTagNameClass(
137
                                $lexerTag->getStringMatch(2),
138
                                $tokens,
139
                                $this->parser
140
                            );
141
                        }
142
                    }
143
144
                    if ($node !== null) {
145
                        if (!$node instanceof AbstractTag) {
146
                            throw new InvalidArgumentException(sprintf(
147
                                'Tag class [%s] must extends base classes [%s] or [%s]',
148
                                get_class($node),
149
                                AbstractTag::class,
150
                                AbstractBlock::class
151
                            ));
152
                        }
153
154
                        $this->nodeList[] = $node;
155
156
                        if ($lexerTag->getStringMatch(1) === 'extends') {
157
                            return;
158
                        }
159
                    } else {
160
                        $this->unknownTag($lexerTag->getStringMatch(1), $lexerTag->getStringMatch(2), $tokens);
161
                    }
162
                } else {
163
                    throw new ParseException(sprintf(
164
                        'Tag [%s] was not properly terminated (won\'t match [%s])',
165
                        $token,
166
                        $lexerTag
167
                    ));
168
                }
169
            } elseif ($lexerVariableStart->match($token)) {
170
                $this->whitespaceHandler($token);
171
                $this->nodeList[] = $this->createVariable($token);
172
            } else {
173
                // This is neither a tag or a variable, proceed with an ltrim
174
                if ($this->trimWhitespace) {
175
                    $token = ltrim($token);
176
                }
177
178
                $this->trimWhitespace = false;
179
                $this->nodeList[] = $token;
180
            }
181
        }
182
183
        $this->assertMissingDelimiter();
184
    }
185
186
    /**
187
     * {@inheritdoc}
188
    */
189
    public function render(Context $context): string
190
    {
191
        return $this->renderAll($this->nodeList, $context);
192
    }
193
194
    /**
195
     * Renders all the given node list's nodes
196
     * @param AbstractTag[]|Variable[]|string[] $list
197
     * @param Context $context
198
     * @return string
199
     */
200
    protected function renderAll(array $list, Context $context): string
201
    {
202
        $result = '';
203
        foreach ($list as $token) {
204
            $value = $token;
205
            if (is_object($token)) {
206
                //Variable or AbstractTag
207
                $value = $token->render($context);
208
            }
209
210
            if (is_array($value)) {
211
                throw new RenderException('Implicit rendering of arrays not supported.'
212
                        . ' Use index operator.');
213
            }
214
215
            $result .= $value;
216
217
            if ($context->hasRegister('break') || $context->hasRegister('continue')) {
218
                break;
219
            }
220
221
            $context->tick();
222
        }
223
224
        return $result;
225
    }
226
227
    /**
228
     * An action to execute when the end tag is reached
229
     * @return void
230
     */
231
    protected function endTag(): void
232
    {
233
        //Do nothing now
234
    }
235
236
    /**
237
     * Handler for unknown tags
238
     * @param string $tag
239
     * @param string $param
240
     * @param array<int, string> $tokens
241
     * @return void
242
     * @throws ParseException
243
     */
244
    protected function unknownTag(string $tag, string $param, array $tokens): void
0 ignored issues
show
The parameter $param is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

244
    protected function unknownTag(string $tag, /** @scrutinizer ignore-unused */ string $param, array $tokens): void

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
245
    {
246
        switch ($tag) {
247
            case 'else':
248
                throw new ParseException(sprintf(
249
                    '[%s] does not expect "else" tag',
250
                    $this->getTagName()
251
                ));
252
            case 'end':
253
                throw new ParseException(sprintf(
254
                    '"end" is not a valid delimiter for [%s] tags.Use [%s]',
255
                    $this->getName(),
256
                    $this->blockDelimiter()
257
                ));
258
            default:
259
                throw new ParseException(sprintf(
260
                    'Unknown template tag [%s]',
261
                    $tag
262
                ));
263
        }
264
    }
265
266
    /**
267
     * This method is called at the end
268
     * of parsing, and will throw an error unless this method is sub classed.
269
     * @return void
270
     * @throws ParseException
271
     */
272
    protected function assertMissingDelimiter(): void
273
    {
274
        throw new ParseException(sprintf(
275
            '[%s] tag was never closed',
276
            $this->getTagName()
277
        ));
278
    }
279
280
    /**
281
     * Returns the string that delimits the end of the block
282
     * @return string
283
     */
284
    protected function blockDelimiter(): string
285
    {
286
        return 'end' . $this->getTagName();
287
    }
288
289
    /**
290
     * Handle the white space.
291
     * @param string $token
292
     * @return void
293
     */
294
    protected function whitespaceHandler(string $token): void
295
    {
296
        $char = Token::WHITESPACE_CONTROL;
297
        if (substr($token, 2, 1) === $char) {
298
            $previousToken = end($this->nodeList);
299
            if (is_string($previousToken)) {
300
                // this can also be a tag or a variable
301
                $this->nodeList[key($this->nodeList)] = ltrim($previousToken);
302
            }
303
        }
304
305
        $this->trimWhitespace = substr($token, -3, 1) === $char;
306
    }
307
308
    /**
309
     * Create a variable for the given token
310
     * @param string $token
311
     * @return Variable
312
     * @throws ParseException
313
     */
314
    private function createVariable(string $token): Variable
315
    {
316
        $variableRegex = '/^'
317
                            . Token::VARIABLE_OPEN
318
                            . Token::WHITESPACE_CONTROL
319
                            . '?(.*?)'
320
                            . Token::WHITESPACE_CONTROL
321
                            . '?'
322
                            . Token::VARIABLE_CLOSE
323
                            . '$/';
324
        $lexer = new Lexer($variableRegex);
325
326
        if ($lexer->match($token)) {
327
            return new Variable($lexer->getStringMatch(1), $this->parser);
328
        }
329
330
        throw new ParseException(sprintf(
331
            'Variable [%s] was not properly terminated',
332
            $token
333
        ));
334
    }
335
}
336