Proyectos de Subversion Moodle

Rev

| Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 1
<?php
2
 
3
/*
4
 * This file is part of Mustache.php.
5
 *
6
 * (c) 2010-2017 Justin Hileman
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
 
12
/**
13
 * Mustache Parser class.
14
 *
15
 * This class is responsible for turning a set of Mustache tokens into a parse tree.
16
 */
17
class Mustache_Parser
18
{
19
    private $lineNum;
20
    private $lineTokens;
21
    private $pragmas;
22
    private $defaultPragmas = array();
23
 
24
    private $pragmaFilters;
25
    private $pragmaBlocks;
26
 
27
    /**
28
     * Process an array of Mustache tokens and convert them into a parse tree.
29
     *
30
     * @param array $tokens Set of Mustache tokens
31
     *
32
     * @return array Mustache token parse tree
33
     */
34
    public function parse(array $tokens = array())
35
    {
36
        $this->lineNum    = -1;
37
        $this->lineTokens = 0;
38
        $this->pragmas    = $this->defaultPragmas;
39
 
40
        $this->pragmaFilters = isset($this->pragmas[Mustache_Engine::PRAGMA_FILTERS]);
41
        $this->pragmaBlocks  = isset($this->pragmas[Mustache_Engine::PRAGMA_BLOCKS]);
42
 
43
        return $this->buildTree($tokens);
44
    }
45
 
46
    /**
47
     * Enable pragmas across all templates, regardless of the presence of pragma
48
     * tags in the individual templates.
49
     *
50
     * @internal Users should set global pragmas in Mustache_Engine, not here :)
51
     *
52
     * @param string[] $pragmas
53
     */
54
    public function setPragmas(array $pragmas)
55
    {
56
        $this->pragmas = array();
57
        foreach ($pragmas as $pragma) {
58
            $this->enablePragma($pragma);
59
        }
60
        $this->defaultPragmas = $this->pragmas;
61
    }
62
 
63
    /**
64
     * Helper method for recursively building a parse tree.
65
     *
66
     * @throws Mustache_Exception_SyntaxException when nesting errors or mismatched section tags are encountered
67
     *
68
     * @param array &$tokens Set of Mustache tokens
69
     * @param array $parent  Parent token (default: null)
70
     *
71
     * @return array Mustache Token parse tree
72
     */
73
    private function buildTree(array &$tokens, array $parent = null)
74
    {
75
        $nodes = array();
76
 
77
        while (!empty($tokens)) {
78
            $token = array_shift($tokens);
79
 
80
            if ($token[Mustache_Tokenizer::LINE] === $this->lineNum) {
81
                $this->lineTokens++;
82
            } else {
83
                $this->lineNum    = $token[Mustache_Tokenizer::LINE];
84
                $this->lineTokens = 0;
85
            }
86
 
87
            if ($this->pragmaFilters && isset($token[Mustache_Tokenizer::NAME])) {
88
                list($name, $filters) = $this->getNameAndFilters($token[Mustache_Tokenizer::NAME]);
89
                if (!empty($filters)) {
90
                    $token[Mustache_Tokenizer::NAME]    = $name;
91
                    $token[Mustache_Tokenizer::FILTERS] = $filters;
92
                }
93
            }
94
 
95
            switch ($token[Mustache_Tokenizer::TYPE]) {
96
                case Mustache_Tokenizer::T_DELIM_CHANGE:
97
                    $this->checkIfTokenIsAllowedInParent($parent, $token);
98
                    $this->clearStandaloneLines($nodes, $tokens);
99
                    break;
100
 
101
                case Mustache_Tokenizer::T_SECTION:
102
                case Mustache_Tokenizer::T_INVERTED:
103
                    $this->checkIfTokenIsAllowedInParent($parent, $token);
104
                    $this->clearStandaloneLines($nodes, $tokens);
105
                    $nodes[] = $this->buildTree($tokens, $token);
106
                    break;
107
 
108
                case Mustache_Tokenizer::T_END_SECTION:
109
                    if (!isset($parent)) {
110
                        $msg = sprintf(
111
                            'Unexpected closing tag: /%s on line %d',
112
                            $token[Mustache_Tokenizer::NAME],
113
                            $token[Mustache_Tokenizer::LINE]
114
                        );
115
                        throw new Mustache_Exception_SyntaxException($msg, $token);
116
                    }
117
 
118
                    if ($token[Mustache_Tokenizer::NAME] !== $parent[Mustache_Tokenizer::NAME]) {
119
                        $msg = sprintf(
120
                            'Nesting error: %s (on line %d) vs. %s (on line %d)',
121
                            $parent[Mustache_Tokenizer::NAME],
122
                            $parent[Mustache_Tokenizer::LINE],
123
                            $token[Mustache_Tokenizer::NAME],
124
                            $token[Mustache_Tokenizer::LINE]
125
                        );
126
                        throw new Mustache_Exception_SyntaxException($msg, $token);
127
                    }
128
 
129
                    $this->clearStandaloneLines($nodes, $tokens);
130
                    $parent[Mustache_Tokenizer::END]   = $token[Mustache_Tokenizer::INDEX];
131
                    $parent[Mustache_Tokenizer::NODES] = $nodes;
132
 
133
                    return $parent;
134
 
135
                case Mustache_Tokenizer::T_PARTIAL:
136
                    $this->checkIfTokenIsAllowedInParent($parent, $token);
137
                    //store the whitespace prefix for laters!
138
                    if ($indent = $this->clearStandaloneLines($nodes, $tokens)) {
139
                        $token[Mustache_Tokenizer::INDENT] = $indent[Mustache_Tokenizer::VALUE];
140
                    }
141
                    $nodes[] = $token;
142
                    break;
143
 
144
                case Mustache_Tokenizer::T_PARENT:
145
                    $this->checkIfTokenIsAllowedInParent($parent, $token);
146
                    $nodes[] = $this->buildTree($tokens, $token);
147
                    break;
148
 
149
                case Mustache_Tokenizer::T_BLOCK_VAR:
150
                    if ($this->pragmaBlocks) {
151
                        // BLOCKS pragma is enabled, let's do this!
152
                        if (isset($parent) && $parent[Mustache_Tokenizer::TYPE] === Mustache_Tokenizer::T_PARENT) {
153
                            $token[Mustache_Tokenizer::TYPE] = Mustache_Tokenizer::T_BLOCK_ARG;
154
                        }
155
                        $this->clearStandaloneLines($nodes, $tokens);
156
                        $nodes[] = $this->buildTree($tokens, $token);
157
                    } else {
158
                        // pretend this was just a normal "escaped" token...
159
                        $token[Mustache_Tokenizer::TYPE] = Mustache_Tokenizer::T_ESCAPED;
160
                        // TODO: figure out how to figure out if there was a space after this dollar:
161
                        $token[Mustache_Tokenizer::NAME] = '$' . $token[Mustache_Tokenizer::NAME];
162
                        $nodes[] = $token;
163
                    }
164
                    break;
165
 
166
                case Mustache_Tokenizer::T_PRAGMA:
167
                    $this->enablePragma($token[Mustache_Tokenizer::NAME]);
168
                    // no break
169
 
170
                case Mustache_Tokenizer::T_COMMENT:
171
                    $this->clearStandaloneLines($nodes, $tokens);
172
                    $nodes[] = $token;
173
                    break;
174
 
175
                default:
176
                    $nodes[] = $token;
177
                    break;
178
            }
179
        }
180
 
181
        if (isset($parent)) {
182
            $msg = sprintf(
183
                'Missing closing tag: %s opened on line %d',
184
                $parent[Mustache_Tokenizer::NAME],
185
                $parent[Mustache_Tokenizer::LINE]
186
            );
187
            throw new Mustache_Exception_SyntaxException($msg, $parent);
188
        }
189
 
190
        return $nodes;
191
    }
192
 
193
    /**
194
     * Clear standalone line tokens.
195
     *
196
     * Returns a whitespace token for indenting partials, if applicable.
197
     *
198
     * @param array $nodes  Parsed nodes
199
     * @param array $tokens Tokens to be parsed
200
     *
201
     * @return array|null Resulting indent token, if any
202
     */
203
    private function clearStandaloneLines(array &$nodes, array &$tokens)
204
    {
205
        if ($this->lineTokens > 1) {
206
            // this is the third or later node on this line, so it can't be standalone
207
            return;
208
        }
209
 
210
        $prev = null;
211
        if ($this->lineTokens === 1) {
212
            // this is the second node on this line, so it can't be standalone
213
            // unless the previous node is whitespace.
214
            if ($prev = end($nodes)) {
215
                if (!$this->tokenIsWhitespace($prev)) {
216
                    return;
217
                }
218
            }
219
        }
220
 
221
        if ($next = reset($tokens)) {
222
            // If we're on a new line, bail.
223
            if ($next[Mustache_Tokenizer::LINE] !== $this->lineNum) {
224
                return;
225
            }
226
 
227
            // If the next token isn't whitespace, bail.
228
            if (!$this->tokenIsWhitespace($next)) {
229
                return;
230
            }
231
 
232
            if (count($tokens) !== 1) {
233
                // Unless it's the last token in the template, the next token
234
                // must end in newline for this to be standalone.
235
                if (substr($next[Mustache_Tokenizer::VALUE], -1) !== "\n") {
236
                    return;
237
                }
238
            }
239
 
240
            // Discard the whitespace suffix
241
            array_shift($tokens);
242
        }
243
 
244
        if ($prev) {
245
            // Return the whitespace prefix, if any
246
            return array_pop($nodes);
247
        }
248
    }
249
 
250
    /**
251
     * Check whether token is a whitespace token.
252
     *
253
     * True if token type is T_TEXT and value is all whitespace characters.
254
     *
255
     * @param array $token
256
     *
257
     * @return bool True if token is a whitespace token
258
     */
259
    private function tokenIsWhitespace(array $token)
260
    {
261
        if ($token[Mustache_Tokenizer::TYPE] === Mustache_Tokenizer::T_TEXT) {
262
            return preg_match('/^\s*$/', $token[Mustache_Tokenizer::VALUE]);
263
        }
264
 
265
        return false;
266
    }
267
 
268
    /**
269
     * Check whether a token is allowed inside a parent tag.
270
     *
271
     * @throws Mustache_Exception_SyntaxException if an invalid token is found inside a parent tag
272
     *
273
     * @param array|null $parent
274
     * @param array      $token
275
     */
276
    private function checkIfTokenIsAllowedInParent($parent, array $token)
277
    {
278
        if (isset($parent) && $parent[Mustache_Tokenizer::TYPE] === Mustache_Tokenizer::T_PARENT) {
279
            throw new Mustache_Exception_SyntaxException('Illegal content in < parent tag', $token);
280
        }
281
    }
282
 
283
    /**
284
     * Split a tag name into name and filters.
285
     *
286
     * @param string $name
287
     *
288
     * @return array [Tag name, Array of filters]
289
     */
290
    private function getNameAndFilters($name)
291
    {
292
        $filters = array_map('trim', explode('|', $name));
293
        $name    = array_shift($filters);
294
 
295
        return array($name, $filters);
296
    }
297
 
298
    /**
299
     * Enable a pragma.
300
     *
301
     * @param string $name
302
     */
303
    private function enablePragma($name)
304
    {
305
        $this->pragmas[$name] = true;
306
 
307
        switch ($name) {
308
            case Mustache_Engine::PRAGMA_BLOCKS:
309
                $this->pragmaBlocks = true;
310
                break;
311
 
312
            case Mustache_Engine::PRAGMA_FILTERS:
313
                $this->pragmaFilters = true;
314
                break;
315
        }
316
    }
317
}